Props
In React, the flow of data is unidirectional: it flows from the parent (higher-level) component to the child (lower-level) component. In this flow, data passed from the parent to the child is conveyed through an object called props
, which is short for "properties."
Figure 1 shows an example of how data is passed in the current vending machine example, where the VendingMachine
(parent component) holds information about the drinks in the vending machine. The VendingMachine
passes this data to the Drink
(child component), which is used to render the drink details.
In this case, the VendingMachine
passes drink information (name, quantity, price, etc.) to the Drink
component via props, where each drink's details are passed as an object. While the example shows only object data being passed, JavaScript allows functions (which are also objects) to be passed via props, and the child component can also pass props to its own child components.
Example: Vending Machine
Let’s look at the vending machine example to understand how props work. In this example, VendingMachine.js
is the highest-level parent component, with child components Drink.js
(for displaying drinks), InputPanel.js
(for entering money), and ExitShelf.js
(for dispensing drinks).
VendingMachine.js
: The top-level parent componentDrink.js
: The component displaying the drink itemsInputPanel.js
: The component for entering the amount of moneyExitShelf.js
: The component where the selected drink is dispensed
In the current vending machine example, the Drink.js
component is simply responsible for receiving information about the drinks (name, price, quantity, etc.) from the VendingMachine.js
(parent component) via props and rendering it. The management of drink data (like updating quantity or setting prices) is handled by the VendingMachine.js
parent component.
In the constructor
of VendingMachine.js
, the drink data is stored in the state:
class VendingMachine extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
name: 'coke',
price: 1000,
quantity: 30
},
{
name: 'cider',
price: 1100,
quantity: 20
},
{
name: 'water',
price: 900,
quantity: 40
},
{
name: 'coffee',
price: 1000,
quantity: 20
},
...
]
}
}
render() {
// Render logic here
}
}
Passing Props
Props are passed from a parent component to a child component by specifying the prop name in the JSX of the parent and assigning it a value, typically from the parent’s state.
For example, in the VendingMachine
component, we pass the details of the drink (as an item
prop) to the Drink
component:
class VendingMachine extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
name: 'coke',
price: 1000,
quantity: 30
},
// Other items
]
}
}
render() {
const { items } = this.state;
return (
...
<Drink item={items[0]} />
...
);
}
}
To render all the items in the VendingMachine
state, we loop through the array of items and pass each one to a Drink
component:
class VendingMachine extends React.Component {
constructor(props) {
// Initial setup
}
render() {
const { items } = this.state;
const layout = [];
for (let i = 0; i < items.length; i++) {
layout.push(
<SegmentGroup horizontal>
<Drink item={items[i++]} />
<Drink item={items[i++]} />
<Drink item={items[i++]} />
<Drink item={items[i++]} />
</SegmentGroup>
);
}
return (
...
{layout}
...
);
}
}
Using Props
So far, we’ve only looked at how props are passed from VendingMachine
to Drink
. Now, let's see how the child component uses the passed props.
In the Drink
component (which is a class component), the props are accessed via this.props
, and the individual properties can be destructured as needed:
class Drink extends React.Component {
render() {
const { item } = this.props; // Accessing the 'item' prop
return (
...
);
}
}
In functional components, props are passed as function parameters, and we can destructure them directly:
// Arrow function
const Drink = ({ item }) => {
return (
...
);
};
// Regular function
const Drink = function(props) {
const { item } = props;
return (
...
);
};
Default Props
In the previous examples, we added a special empty
object to handle cases where no drink is available. However, if no valid item
is passed, null
could be passed to the Drink
component, which would cause errors when accessing properties like name
, price
, and quantity
.
To avoid this, we can set default values for props using defaultProps
:
// Class Component
class Drink extends React.Component {
static defaultProps = {
item: {
name: 'empty',
price: 0,
quantity: 0
}
};
render() {
// Rendering logic here
}
}
// Functional Component
const Drink = ({ item }) => {
return (
...
);
};
Drink.defaultProps = {
item: {
name: 'empty',
price: 0,
quantity: 0
}
};
Prop Types
In JavaScript, there are no built-in type checks, and React also allows flexible typing. However, this flexibility can sometimes lead to bugs and make debugging difficult. To prevent this, you can use the prop-types
library to specify which props are required and what type of data they should be.
If a prop is passed with a type that doesn’t match the expected type, React will log a warning to the console. Here’s how you can define propTypes
for your component:
import PropTypes from 'prop-types';
class Drink extends React.Component {
// Component logic here
}
const itemShape = {
name: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
quantity: PropTypes.number.isRequired
};
Drink.propTypes = {
item: PropTypes.shape(itemShape)
};
ReadOnly
All React components must act like pure functions with respect to their props.
One important rule in React is that props should be treated as immutable (read-only). React's documentation states that props must act like pure functions. This means that you should not modify the props received by a component.
Here’s an example of what not to do: if you try to modify the quantity of a drink directly in the Drink
component, it would violate the immutability of props:
// Incorrect approach
class Drink extends React.Component {
onClickGetButton = () => {
const { item } = this.props;
item.quantity = item.quantity - 1; // Direct modification of props
};
render() {
return (
<Button size="mini" color="red" onClick={() => this.onClickGetButton()}>GET</Button>
);
}
}
Instead, you should update the state in the parent component and pass the updated data back to the child component:
class VendingMachine extends React.Component {
onClickGetButton = (i) => {
this.setState({
items: this.state.items.map((item, index) => {
if (i === index && item.quantity > 0) {
return { ...item, quantity: --item.quantity };
}
return item;
})
});
};
render() {
...
<Drink item={items[i]} onClickGetButton={this.onClickGetButton} index={i++} />
...
}
}
// Correct approach
class Drink extends React.Component {
render() {
const { item, onClickGetButton, index } = this.props;
return (
...
<Button size="mini" color="red" onClick={() => onClickGetButton(index)}>GET</Button>
...
);
}
}
Conclusion
To summarize what we've learned about props:
- React uses unidirectional data flow, where data is passed from the parent to the child component via
props
. - Props are immutable and cannot be directly modified(
ReadOnly
). - You can set default values for props using
defaultProps
and validate them using theprop-types
library.
Thank you for reading!