DEV Community

loading...

Explain lifting state up(React) Like I'm Five

franciscoaguilars profile image Francisco Aguilar ・1 min read

Hey guys, I'm a Rails ''entry'' developer(unemployed) and I am now in my part time trying to learn React, I saw in a tutorial where they ''lift state up'' so the State only lives in the parent component and it passes down to its children component via props, now, how common is that? Is it better that way or should I leave each components with its own state.
I think it's much messy but idk, I'm a begginer!

Discussion

pic
Editor guide
Collapse
vonheikemen profile image
Heiker

Passing props down and lifting state is basically the first tool you have for "state management". Is it common? I believe it should. Can it get messy? Yes, absolutely. That's why React has added features like the "context api" or the new hooks. And also there are 3rd party tools like redux that can help you manage the state.

But you can do a lot using just props and component composition. Here is a video that show how to use component composition.

Collapse
brandinchiu profile image
Brandin Chiu

React, like other newer web frameworks work with "Web Components" -- the idea of encapsulating common functional components of a web app into discrete blocks to be reused.

State management and encapsulation are two different programming concepts that sometimes work against each other.

Let's look at a simple example:

I want to create a PasswordField() component, which is an input field that contains the following functionality:

  • entered text is hidden from view
  • there's an icon at the end of field that if you click it reveals the text

I also want to do some form validation on this field -- specifically we want to know if the entered text matches a specific password format I've specified.

Now, when I try to use my password field, I decide to put it in a form. I only want this form to be submittable if all of the fields in the form validate successfully.

Here's where our problems start -- our validation is encapsulated in the PasswordField() component, which means my form won't know if it validates. This is obviously a problem.

"Lifting state up" in this context, would encourage me to remove my validating state (whether or not my field is valid) by making the form responsible for this state, instead of my component.

This is what we're talking about -- moving state from a child component into the parents that manage it. It makes sense in how we actually use the state, but stands opposed sometimes to the idea of encapsulation. This is one of the reasons why we have so many other state management tools we can work with.

Collapse
dancouper profile image
Dan Couper

Say you have a function. A function, in this context, is a little reusable piece of functionality that takes some instructions (in the form of values), does {something} with them, and returns a value. When you run it, passing it the values it requires, it runs whatever procedure is defined and returns a value.

function add (a, b) {
  return a + b;
}

add takes two numbers and returns the sum of them. add(1,2), that's 3.

That function doesn't store anything, it just does the addition. And if you give it the same arguments it always returns the same thing. A React app is normally made up of lots of functions like this. That's great, as it makes things relatively simple.

const Greeting = ({ name }) => <p>Hello, {name}!</p>

This has a slightly weird syntax, but like add, it's just a function that takes a value and returns a value. The way you call it is a bit different as well -- <Greeting name="Dan" />. And in the browser, there's this: <p>Hello, Dan!</>.

But GUI applications normally need know what happened previously. They need to store the results of user actions somewhere -- they need to handle state.


To go back to add, say you want to add up a collection of numbers. You need the program to remember what the last result was. It needs state. That state can't be stored in the add function, it needs to be stored outside of it.

Functions are slightly special in JavaScript. It's not just the instructions of what a function does that are stored in the computer's memory. It is that plus any variables that are defined inside it.

So with that in mind, what you can do is wrap add in another function. The value of the state (the running total) is defined in that function, and that function can use that and run add as many times as needed.

function sum (numbers) => {
  let total = 0; // the state
  for (const number of numbers) {
    total = add(total, number);
  }
  return total;
}

sum([1,2,3,4,5]). That would be 15!

Note that this way of looping over a collection and running a function on the previous result and the current item happens to already be built into JS, so can just write it as:

function sum (numbers) {
  return numbers.reduce(add, 0);
}

So to go back to React, say you have a function that returns an input.

const Input = () => {
  const [value, setValue] = React.useState("");

  return <input type="text" value={value} onChange={(e) => setValue(e.target.value)} />;
}

And you want to reuse that wherever you need some user input. Without being able to store the result of the input action somewhere those functions really aren't much use. The Input function will allow a user to enter some input, but it's unlikely to be useful in the context of an application.

For example, if you're creating a form, you might have a few inputs, and you need to collect the values from them in one place. So you lift the state up from the inputs to the form function. Very simplified example:

const Input = ({ value, setValue }) => (
  <input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
);

const Form = () => {
  [name, setName] = React.useState("");
  [age, setAge] = React.useState(0);

  return (
    <form onSubmit={doSomethingWithState}>
      <Input value={name} setValue={setName} />
      <Input value={age} setAge={setAge} />
    </form>
  );
};

And you can keep doing this, moving the state upwards through the components. As sibling commenters have noted, this can get messy and difficult to keep track of as you add more and more components. So:

  • always try to keep state as close to where it's used as possible, otherwise you're likely to get a bit confused.
  • if this isn't possible, the Context API (and libraries like Redux) exist to handle state that needs to be stored high up the tree.

n.b. this is a simplification of how and where local state is retained w/r/t state hooks. And a component function (+ JSX) goes through a fair few stages before actually returning a value, it doesn't do it directly. But I would say the principles are exactly the same.