ReactJS: Lifting State Up

If you’ve been working with React for any time, you may have encountered the term “lifting state up.” It’s a common phrase in React development circles, but what does it mean?

What Is State in React?

Firstly, it’s necessary to understand what “state” means within the context of a React application. In React, the state is a JavaScript object that stores the component’s local state or the data that it holds.

It’s mutable; hence, it can be changed over time, mostly in response to user actions. For instance, user input can be stored in a state as the user types or a button can change the state of an app from logged out to logged in when clicked.

What Does “Lifting State Up” Mean?

“Lifting state up” refers to a common pattern in React where the state is moved to a higher-level component – a common ancestor of all components that need access to that state. When this is done, the state becomes shared and can be passed down to child components through props. This concept emerged from React’s one-directional data flow (parent to child interaction) and might be a bit tricky for developers who are more accustomed to two-directional data bindings.

Why Do We Need to Lift State Up?

Imagine a scenario where a couple of sibling components need to share and manipulate the same state. Suppose that state is contained within one of those sibling components. In that case, you’d have to engage in a rather complicated process to share it, thereby violating the DRY (Don’t Repeat Yourself) principle.

That’s where the idea of lifting the state up becomes very useful.

By moving the state up to its closest common ancestor, you allow all sibling components to have access to it just through props.

This makes your application’s data flow more efficient, and individual components become less complex as the state management responsibility is taken away from them.

How to Lift State Up in React?

Let’s take a simple example of two sibling components, ComponentA and ComponentB, which need to change and display the same state.

  1. Create a common state in the nearest common ancestor component.
  2. Then, pass it down to ComponentA and ComponentB as props.
  3. If ComponentA and ComponentB need to update the state, pass down a function from the parent component that can be used to update the state. Remember, the actual mutation is achieved back in the ancestor component, which is carrying the “master” state.
import { useState } from "react"

const MasterComponent = () => {
  const [state, setState] = useState(0)

  return (
    <>
      <ComponentA state={state} setState={setState} />
      <ComponentB state={state} setState={setState} />
    </>
  )
}

const ComponentA = ({
  state,
  setState,
}: {
  state: number
  setState: (arg: number) => void
}) => {
  return (
    <>
      <div>{state}</div>
      <button onClick={() => setState(state + 1)}>Count</button>
    </>
  )
}

const ComponentB = ({
  state,
  setState,
}: {
  state: number
  setState: (arg: number) => void
}) => {
  return (
    <>
      <div>{state}</div>
      <button onClick={() => setState(state + 1)}>Count</button>
    </>
  )
}

In summary, “lifting state up” is about centralizing the management and manipulation of the state to a single parent component rather than scattering the state across multiple sibling components.

This approach makes your application more maintainable, understandable, and testable, as the state changes are predictable and easier to trace.

It’s essential to note, however, that for larger applications, at some point, lifting state up and plumbing props through your application can become cumbersome. Luckily, more advanced state management solutions are available, like Redux, MobX, or React’s Context API, which can provide more straightforward state management in more complex situations.

However, the basic idea – lifting state up – remains the same: to place your state in the right place on the hierarchy of your app components.

Leave a Comment