If I were to point out a single concept that is critical to understand in React development, it would be state management.
So what is React state management? How is it different from managing data in C# or Java? Let’s find out! 🙂
If you are a backend or desktop developer, you may have never come across the term of state. At least, I never used it before learning frontend web development. Surely not in the context it is used in frameworks like React. The term more familiar to me at that time was data binding known from WPF or WinForms.
This is what WPF documentation says:
Data binding is the process that establishes a connection between the app UI and the data it displays. If the binding has the correct settings and the data provides the proper notifications, when the data changes its value, the elements that are bound to the data reflect changes automatically
In case of WPF, the crucial part to me is If the binding has the correct settings and the data provides the proper notifications (…). If you have ever worked with WPF, you know how frustrating can these notifications be (any
INotifyPropertyChanged fans here?).
WPF’s state management it not that bad. However, React has something better 😉
When the state changes, your UI is updated by React. In other words – in React, UI is a function of state:
It means that your React app (or its individual pieces called components) take some state and output the actual, rendered UI.
This is React state management in a nutshell. Let’s explore it a bit more 😉
Small disclaimer: I will focus on state in React functional componets. I consider class components somewhat legacy. I think this meme summarizes it best 😀:
The Naive Way
Let’s try to approach React state management as C# developer. In React, the basic piece of UI we create is a component. Let’s add one:
For now, we render some text and a button which does nothing. What we want is to have some data displayed, like a number, which is incremented on every button click. Let’s try to implement that naively:
Now, let’s check if it works:
It looks nothing happens To investigate that, we can add a
How does that look in the console now?
Woooow, something is really going wrong here! The data is updated in memory, but not reflected in the UI 🤔
Why does that happen? Because
myCount is not a state. It’s just some variable we created hoping that it will work fine as part of the UI. Remember what we have discussed in the previous section – we need to start thinking in React. This is The React way, not Java way or C# way anymore.
The React Way
So, how to use React state management properly in our example? We need to make
myCount be a part of component’s state. The simplest way to do that looks as follows:
First, instead of declaring a “normal” variable for storing our UI-related data, we used the
useState hook. As you can see, it returns an array with two things: our state variable (
myCount) and a function to update its value
setMyCount). We also set the initial value to
0. A good practice is to always use a construct like
[myCount, setMyCount] to destructure what
useState returns into named, separate objects.
Now we are managing our component’s state The React Way. Does it work? Let’s see:
Great! We have just learned how the basic form of state management works in React. That was easy, wasn’t it? 😉
Now, let us discuss one more aspect related to the state itself.
Reference vs value
We know the React Way for state management now. We feel confident. However, we are also used to how data is managed in C# or Java, especially in terms of handling reference and value types. This knowledge will come very handy now 🙂
Let’s try to create a bit more complex component:
Now, instead of storing a single value in our component’s state, we store a whole object of type
Person. The object has a few properties:
There is also a button. We want this button to take the current
john object, increment the value of
age on it and update the state. We do it The React Way using
setJohn function returned by
useState. Just to be sure, we also have the
console.log(john) just before updating the state. Does that work as we expected?
Eh, what’s happening here this time? We can clearly see that
age is updated on the object on each click, but why isn’t the UI re-rendered? 🤔
As I mentioned before, it’s critical to understand the difference between reference and value types here. If you don’t know it, go and make up for it now.
The reason it does not work is that we haven’t updated the actual content of
john state variable by calling
setJohn(currentJohnObject). What is really being hold in
john variable is the reference (pointer, address in memory) to the actual object. And this is what React tracks. We didn’t really update it, because we assigned the same instance of the object to the state. It doesn’t matter that we updated its internal property
age – the reference itself wasn’t updated.
How do we solve that? Well, in current solution we’d need to create a copy of
john object before using
setJohn to alter the state. In effect, we’ll get a new instance of the object in memory and
john variable will hold a new reference (address) to it.
So, if we only change our
onClick implementation to this one:
Everything starts to work as expected:
I think you now understand how React state management works. I also hope that you see the issues managing state in such a way can bring. Even in this trivial example, we create a new instance of an object on every button click. Apart from memory usage considerations, this just looks ugly. Imagine manually creating copies of much more complex objects, like classes storing arrays or dictionaries. This gets pretty wild 🤪
We will see how this issue can be addressed by the end of this article. But first, let’s discuss
props – another concept critical to really understand React state management.
state, we also use
props to control the state of our React components. The name is a short for properties, which quite well describes its purpose.
Props are the data input provided to a component from outside (from a parent component). They are immutable and cannot be changed inside the component which receives them. Let’s see some examples.
Sharing state between components
The most common usage for
props is sharing state between components. Imagine that you have an input, where the user manually enters some data. You want to use the current value of this input in two other components. This is where you need to share your component’s state (the value of the input) with these two other components. We often call it lifting the state up – you keep your state higher in the components tree, so the child components can use it.
Let’s try to see an example of connecting
As you can see, at lines 4 and 9 we manage the state variable
age. This is the old stuff we have already seen.
However, at lines 27 and 28 we are passing the
age state variable into
YearsUntilCentenarian children components as
currentAge prop. This is how the
DaysLiving component is implemented:
props is simply a typed object you pass to a function component. To make things easier, you can also use object destructuring and accept the props in the following way:
That way, you don’t need to write
props.currentAge every time you want to use this property inside the component.
Does changing props always trigger a re-render?
Well, generally the answer to this question is yes. Every time a prop of a component changes, this component will get re-rendered. However, this is not entirely true 🤪
To make this belief true, we need to assume that this prop is changed in a React way. So, to take things literally, consider this component we saw before:
From its perspective only, it accepts a
currentAge property. For this component to re-render, it’s not enough that the
currentAge property changes in any way. Its value must be changed by mutating the state in the parent component.
So, if the value provided as
DaysLiving component changes in the parent component by properly mutating the state (with
setAge mutation callback, in our case):
the child component (
DaysLiving) will get re-rendered.
This is because when a parent component changes, React by default re-renders the parent component itself and all of its children.
If you modified the
age variable in the parent compont directly, without using
setAge (which we already know it’s incorrect), the
currentAge prop would technically change, but the
DaysLiving component will not get re-rendered.
state vs props
This is a question that people oftern ask: what’s the difference between
state in React? 🤔
I would start with addressing this differently: what do
state have in common?
As you already know, both are used for React state management. Both of them influence when the components will be re-rendered by React. Props and state are the absolute foundations of React.
The main difference is that
state is used internally in a component, while
props are used to pass information from parent to children components.
State is also naturally mutable within a component where it’s defined, but
props are immutable and cannot be changed in a component that receives and uses them.
React state management libraries
That’s basically everything you need to know to be able to comfortably work with React state management. However, I mentioned before that managing state using only built-in React mechanisms actually sucks 🤪 That’s why I’d like to make a short point on state management libraries.
In my opinion, you can easily start working with React using the built-in state management mechanisms we discussed today. It’s good to know how it works in practice. However, the truth is that as your application grows and you want to keep it scalable and maintenable, you will need a state management library at some point. As soon as your components tree gets bigger, sharing data and state will not be as easy as “passing props to a children component”. You will need to share state between multiple components in the tree, sometimes not only between parent and children, but also with another components defined on the same level or even in a totally different place of the tree.
This is where React state management libraries come very handy. They solve many of the React’s issues that come to the surface as your state gets complex. But even if you use one, you will still always need
props. That’s why it’s critical to understand these two basic concepts first and play with them for some time.
If you’re starting out, it’s good to know that state management libraries exist and that one day you will have to get your hands on one (or more 😉) of them. I hope to share my thoughts on state management libraries in a separate article one day.
That’s it! Now you should know how React manages state of its components. Of course, we didn’t cover everything related to state management in React, but I hope you got the basics 😉
You can find all of the source code used in this article here.
How do you manage state in your React apps? Do you use a state management library? If yes, which one and why? Let me know in the comments below!