This content originally appeared on DEV Community and was authored by davinceleecode
In React, managing state is one of the most essential concepts to master when building dynamic user interfaces. This guide will cover how to properly use useState, how to update nested state immutably, and the best practices when working with props.
What is useState
in React?
useState
is a Hook that allows you to add state to functional components. Here’s a simple example:
const [count, setCount] = useState(0);
In this case, count
is a state variable, and setCount is the function to update it.
Example with Nested State
Sometimes, you’ll work with objects or arrays in your state. Here’s an example with a nested object:
const [games, setGames] = useState([
{
id: 1,
address: "Philippines",
player: {
name: "Vincent Lee",
age: 33,
},
items: [
{ id: 1, title: "Item 1" },
{ id: 2, title: "Item 2" },
],
},
]);
Let’s say we want to update the title
of an item
with id === 2
. We must update it immutably:
const onClickUpdateProd = () => {
setGames(
games.map((game) =>
game.id === 1
? {
...game,
items: game.items.map((item) =>
item.id === 2 ? { ...item, title: "new item cc" } : item
),
}
: game
)
);
};
Always avoid mutating the state directly! Instead, return a new copy with the updated value.
What About Props?
Props are the values you pass from a parent component to a child component. A golden rule in React:
We should treat the props as immutable or readonly.
All changes to the state must be done in the component that owns the state. Child components should never modify the props directly.
Why?
This keeps your UI predictable and easier to debug. Here’s a breakdown:
- Parent holds the state.
- Child receives props and uses them.
- Updates happen in the parent and flow down via props.
// Parent.js
const [name, setName] = useState("John");
return <Child name={name} />;
// Child.js
const Child = ({ name }) => {
return <p>{name}</p>; // Use, not modify
};
If the child needs to trigger a change, pass down a function from the parent:
/ Parent.js
<Child name={name} updateName={() => setName("New Name")} />
// Child.js
<button onClick={updateName}>Change Name</button>
Working with Deeply Nested Objects
When dealing with deeply nested state, maintaining immutability manually can become challenging. For example, updating a nested array inside an object inside another array may lead to a lot of spread operators (...
) and complex logic.
Solution: Use a Library like Immer
To simplify immutable state updates, you can use libraries like:
- Immer: Simplifies immutable updates with a mutable-like syntax.
- Immutable.js: A more strict and structural approach to immutability.
Here’s an example using Immer:
import produce from "immer";
const onClickUsingImmer = () => {
setGames(
produce((draft) => {
const game = draft.find((g) => g.id === 1);
if (game) {
const item = game.items.find((i) => i.id === 2);
if (item) {
item.title = "new item cc";
}
}
})
);
};
Immer handles the copying and updating behind the scenes, so you can write updates more naturally.
Summary
-
useState
is used to add and manage state in functional components. - Never mutate state directly; always update immutably.
- Props are read-only. State updates must be handled where the state is defined.
- For complex or deeply nested updates, libraries like Immer or Immutable.js can help.
By following these best practices, you’ll build more reliable and scalable React applications.
Happy coding!
If you found this helpful, consider supporting my work at Buy Me a Coffee.
This content originally appeared on DEV Community and was authored by davinceleecode