What Is “Lifting State Up” in React?

Here is a simple, practical example of what the React concept of “lifting state up” is and how it can help you in building your applications.

Lifting state up is a common pattern that is essential for React developers to know, because it helps us avoid more complex (and often unnecessary) patterns for managing our state.

How does it do that? Let’s see how through a simple example.

Want to build amazing React apps using best practices from front to back? Check out the React Bootcamp.

Breaking down our Todo App

Let’s start with a basic todo application, which consists of three components: TodoCount, TodoList and AddTodo.

All of these components, as their name suggests, are going to need to share some common state.

If we look at TodoCount, this is where we’re going to display up at the top of our app, how many total to dues we have in our application.

TodoList is going to be where we show all of our todos. It has some initial state with these three items (“item 1”, “item 2”, “item 3”), which we’re displaying within an unordered list.

And finally, we have AddTodo. This consists of  a form, where we want to be able to add a new item to this list. Right now we’re just logging the todo that we type into the input to the console:

// src/App.js

import React from "react";

export default function App() {
  return (
    <>
      <TodoCount />
      <TodoList />
      <AddTodo />
    </>
  );
}

function TodoCount() {
  return <div>Total Todos: </div>;
}

function TodoList() {
  const [todos, setTodos] = React.useState(["item 1", "item 2", "item 3"]);

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo}>{todo}</li>
      ))}
    </ul>
  );
}

function AddTodo() {
  function handleSubmit(event) {
    event.preventDefault();
    const todo = event.target.elements.todo.value;
    console.log(todo);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" id="todo" />
      <button type="submit">Add Todo</button>
    </form>
  );
}

Why do we care about lifting state up?

How can we use the concept of lifting state up to help finish our application?

These components need to share some state, some todo state. We need to share that todo state order to display the number of todos as well as to update our todo list.

This is where the concept of lifting state up comes in.

We lift up state to a common ancestor of components that need it, so that they can all share in the state. This allows us to more easily share state among all of these components that need rely upon it.

What common ancestor should we lift up our state to so all of the components can read from and update that state? The App component.

Here’s what our app should now look like:

// src/App.js

import React from "react";

export default function App() {
  const [todos, setTodos] = React.useState(["item 1", "item 2", "item 3"]);    
    
  return (
    <>
      <TodoCount />
      <TodoList />
      <AddTodo />
    </>
  );
}

function TodoCount() {
  return <div>Total Todos: </div>;
}

function TodoList() {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo}>{todo}</li>
      ))}
    </ul>
  );
}

function AddTodo() {
  function handleSubmit(event) {
    event.preventDefault();
    const todo = event.target.elements.todo.value;
    console.log(todo);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" id="todo" />
      <button type="submit">Add Todo</button>
    </form>
  );
}

Passing state down

However, there’s a small problem.

TodoList doesn’t have access to the todos state variable, so we need to pass that down from App:

We can do that with components in React using props.

On TodoList, let’s add a prop named todos. We can destructure todos from the props object. This allows us to see our todo items once again.

Now what about displaying the total number of todos within our to do count component?

This is another instance in which we can pass down the  data as a prop, since to do count relies upon that state. So we’ll again provide the same prop of to dues the structure from to do counts, props object, and to display the total number of to dues. That would be from the value todos.length, and we see that it’s 3 currently.

import React from "react";

export default function App() {
  const [todos, setTodos] = React.useState(["item 1", "item 2", "item 3"]);

  return (
    <>
      <TodoCount todos={todos} />
      <TodoList todos={todos} />
      <AddTodo />
    </>
  );
}

function TodoCount({ todos }) {
  return <div>Total Todos: {todos.length}</div>;
}

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo}>{todo}</li>
      ))}
    </ul>
  );
}

Passing callbacks down

Now the last step is to be able to add a new todo.

This is where our setter function, setTodos, comes in. To update our todo state, we don’t need to pass down both values, the variable and the setter function, all we need to do is pass down setTodos.

We’ll pass it down to addTodo as a prop of the same name (setTodos) and destructure it from props.

As you can see, we’re using our form on submit to get access to the input’s value; whatever was typed into it, which we’re putting it within a local variable named todo.

Instead of needing to pass down the current todos array, we can just use an inner function to get the previous todos value. This allows us to get previous todos and just return what we want the new state to be.

This new state will be an array, in which we will spread all of the previous todos and add our new todo as the last element in that array:

import React from "react";

export default function App() {
  const [todos, setTodos] = React.useState(["item 1", "item 2", "item 3"]);

  return (
    <>
      <TodoCount todos={todos} />
      <TodoList todos={todos} />
      <AddTodo setTodos={setTodos} />
    </>
  );
}

function AddTodo({ setTodos }) {
  function handleSubmit(event) {
    event.preventDefault();
    const todo = event.target.elements.todo.value;
    setTodos(prevTodos => [...prevTodos, todo]);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" id="todo" />
      <button type="submit">Add Todo</button>
    </form>
  );
}

Not only by lifting state up and passing its state variable down to the components that need to read from it, can we use this pattern, we can also use it for callbacks to be able to update state.

Once we add a new item to our todo list, it’s immediately added to state and we see our TodoList component re-render to display that new item, as well as TodoCount to show the total number of todos which is now 4:

Conclusion

Lifting state up is an important pattern for React developers because sometimes we have state that’s located within a particular component that also needs to be shared with sibling components.

Instead of using an entire state management library like Redux or React Context, we can just lift the state up to the closest common ancestor and pass both the state variables the state values down as well as any callbacks to update that state.

Ready for the next step? Join The React Bootcamp

The React Bootcamp takes everything you should know about learning React and bundles it into one comprehensive package, including videos, cheatsheets, plus special bonuses.

Gain the insider information hundreds of developers have already used to master React, find their dream jobs, and take control of their future:

The React Bootcamp
Click here to be notified when it opens

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts