React Props

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."

Example of Passing Props (Figure 1)

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 component
  • Drink.js: The component displaying the drink items
  • InputPanel.js: The component for entering the amount of money
  • ExitShelf.js: The component where the selected drink is dispensed
Vending Machine Component Structure (Figure 2)

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)
};
prop-types Waring Console

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 the prop-types library.

Thank you for reading!

React + MobX
SPA 라이브러리인 React를 MobX state 관리 라이브러리와 함께 배워봅시다.