React State Management Basics
Dawid Sibiński
Posted on October 29, 2023
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! 🙂
State
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 😉
React introduces us to a concept of state. In React components, you do not set anything directly, like calling some code to update an input’s value (this is possible, we’re still in JavaScript world, but it’s not a React way of doing stuff). You don’t directly bind your UI controls with data like you would in WPF or WinForms. Instead, you keep the data in memory, which is a current state of the UI (the whole app or a piece of it).
When the state changes, your UI is updated by React. In other words – in React, UI is a function of state :
UI = fn(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:
import Button from "react-bootstrap/Button";
export const NaiveComponent = () => {
return (
<>
<div>I am a naive component</div>
<Button variant="success">Click me!</Button>
</>
);
};
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:
export const NaiveComponent = () => {
let myCount = 0;
return (
<>
<div>I am a naive component</div>
<div>Current count: {myCount}</div>
<Button
variant="success"
onClick={() => {
myCount++;
}}
>
Click me!
</Button>
</>
);
};
Now, let’s check if it works:
It looks nothing happens 🥹 To investigate that, we can add a console.log
(the most common debugging technique in JavaScript world 😅) to the onClick
event:
onClick={() => {
myCount++;
console.log(myCount);
}}
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:
export const TheReactWayComponent = () => {
const [myCount, setMyCount] = useState(0);
return (
<>
<div>I am a React Way component</div>
<div>Current count: {myCount}</div>
<Button
variant="success"
onClick={() => {
setMyCount(myCount + 1);
}}
>
Click me!
</Button>
</>
);
};
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:
type Person = {
id: number;
name: string;
age: number;
};
export const ObjectHolderComponent = () => {
const johnInitialValue: Person = {
id: 1,
name: "John Doe",
age: 30,
};
const [john, setJohn] = useState<Person>(johnInitialValue);
return (
<>
<div>ID: {john.id}</div>
<div>Name: {john.name}</div>
<div>Age: {john.age}</div>
<Button
variant="success"
onClick={() => {
const currentJohnObject = john;
currentJohnObject.age = currentJohnObject.age + 1;
console.log(currentJohnObject);
setJohn(currentJohnObject);
}}
>
Make John older
</Button>
</>
);
};
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: id
, name
and age
.
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:
onClick={() => {
const johnCopy = { ...john };
johnCopy.age = john.age + 1;
setJohn(johnCopy);
}}
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.
Props
Apart from 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 state
with props
:
export const PersonData = () => {
const minimumAge = 5;
const maximumAge = 100;
const [age, setAge] = useState<number>(minimumAge);
const handleAgeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const enteredAge = parseInt(event.target.value);
if (enteredAge >= minimumAge && enteredAge <= maximumAge) {
setAge(enteredAge);
}
};
return (
<>
<Form>
<Form.Group controlId="age">
<Form.Label>Your age</Form.Label>
<Form.Control
type="number"
value={age}
onChange={handleAgeChange}
min={minimumAge}
max={maximumAge}
/>
</Form.Group>
</Form>
<DaysLiving currentAge={age} />
<YearsUntilCentenarian currentAge={age} />
</>
);
};
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 DaysLiving
and YearsUntilCentenarian
children components as currentAge
prop. This is how the DaysLiving
component is implemented:
type DaysLivingProps = {
currentAge: number;
};
export const DaysLiving = (props: DaysLivingProps) => {
const daysLived = props.currentAge * 365;
return (
<div>
<p>You have lived for {daysLived} days.</p>
</div>
);
};
Notice that 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:
export const DaysLiving = ({currentAge}: DaysLivingProps) => {
const daysLived = currentAge * 365;
/// ... rest of the code
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:
export const DaysLiving = ({currentAge}: DaysLivingProps) => {
const daysLived = currentAge * 365;
return (
<div>
<p>You have lived for {daysLived} days.</p>
</div>
);
};
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 currentAge
to DaysLiving
component changes in the parent component by properly mutating the state (with setAge
mutation callback, in our case):
export const PersonData = () => {
// ...
const [age, setAge] = useState<number>(minimumAge);
const handleAgeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// ...
setAge(enteredAge);
}
};
return (
<>
// ...
<DaysLiving currentAge={age} />
// ...
</>
);
};
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 props
and state
in React? 🤔
I would start with addressing this differently: what do props
and 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.
Maybe you heard about Redux, Zustand or MobX. All of them are state management libraires for React. But why would you even need one? And should you learn one now?
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 state
and 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.
Summary
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!
The post React State Management Basics appeared first on CodeJourney.net.
Posted on October 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.