Lifting State Up

Referennces

Lifting State Up – React official Docs https://reactjs.org/docs/lifting-state-up.html

Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor. Let’s see how this works in action.

There should be a single “source of truth” for any data that changes in a React application. Usually, the state is first added to the component that needs it for rendering. Then, if other components also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on the top-down data flow.

Lifting state involves writing more “boilerplate” code than two-way binding approaches, but as a benefit, it takes less work to find and isolate bugs. Since any state “lives” in some component and that component alone can change it, the surface area for bugs is greatly reduced. Additionally, you can implement any custom logic to reject or transform user input.

If something can be derived from either props or state, it probably shouldn’t be in the state.

Example

Let's see a concrete example. Let say you have 2 components that need to communicate with each other: An InputFilter component and a ProductList component that are both connected within App.

function InputFilter(props) {
  return (
    <input type="text">
  );
}

function ProductList(props) {  
  const productList = props.products.map((product) => {
    return (<li>{product.name}</li>)
  });

  return (
    <ul>
      {productList}
    </ul>
  )
}

class App extends React.Component {
  render() {
    return (
      <div>
        <InputFilter />
        <ProductList collection={products} />
      </div>
    );
  }
}

In this example, the ProductList shows a list of products. Our goal is to filter this list with our InputFilter component based on user input. The problem here is that we have to communicate to ProductList what the user has typed. To do so, we will store the user input in the state of our App. By doing this, we are lifting the state of our InputFilter up into App to be able to share the data and pass it as props in our ProductList.

To do so, we will:

  • Add a userInput state to our App.

  • Initialize the default state

  • Add a callback function that we will pass as props to InputFilter

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userInput: ''
    }
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    this.setState({
      userInput: event.target.value
    })
  }

  render() {
    return (
      <div>
        <InputFilter onChange={this.handleChange}>
        <ProductList collection={products} />
      </div>
    );
  }
}

function InputFilter(props) {
  return (
    <input type="text" onChange={props.onChange}>
  );
}

Every time a user types something in our InputFilter, the component will trigger the callback and will update the state of our parent.

Now that we store the user input in our App, we are able to pass this information to our ProductList component as a prop or even better, pass a filtered collection..

class App extends React.Component {
  ...
  render() {
    const filteredProducts = filterProductByName(products, this.state.userInput)
    return (
      <div>
        <InputFilter onChange={this.handleChange}>
        <ProductList collection={filteredProducts} />
      </div>
    );
  }
}

Last updated