In React, understanding state and state management is crucial. Improper usage can lead to unexpected behaviors in your code. The latest React documentation delves deeper into state, providing me with a much clearer understanding. In this blog, I'll share what I've learned.
- Variables in React
- State
- State as a Snapshot
- Batching
- Updater Function
- Updating Objects in State
- Updating Arrays in State
Variables in React
Local variables are values that exist only within the function where they're declared. They don’t persist between renders. When React re-renders a component (which can be independent, like a button or a form), it starts fresh, meaning local variables are reset to their default values. Because of this, changes to local variables won’t trigger a re-render. If a variable is updated, such as by typing in an input field or clicking a button, React doesn't recognize that a change has occurred, so it doesn't know to update the UI with the new data.
State
To keep track of changes and trigger re-renders, React provides a mechanism called state. Unlike local variables, state persists between renders and changes to it will cause React to re-render the component to reflect the updated values.
To use state in React, first import useState
at the top level:
import { useState } from 'react';
Then, declare a state variable using useState
:
const [something, setSomething] = useState();
Example:
const [index, setIndex] = useState(0);
The square brackets []
represent array destructuring, and useState
always returns two values. The first value (index
in this example) is the state variable, which holds the stored value, and the second value (setIndex
) is the state setter function. This setter function updates the value and triggers React to re-render the component. The "0" is the initial value you set for the state.
A state variable should only be used to store information between re-renders of a component. For example, if an event occurs within a component, such as prompting for a name via a prompt()
window and greeting the user with that name, you don't need state.
Here's an example from the React documentation:
export default function FeedbackForm() { function handleClick() { const name = prompt('What is your name?'); alert(`Hello, ${name}!`); } return ( <button onClick={handleClick}> Greet </button> ); }
State as a Snapshot
Rendering in React occurs initially by calling createRoot
with the target DOM node, and the subsequent re-renders happen every time there's a change in state.
React operates with a concept known as state as a snapshot, which requires us to adjust our mental model. React uses the state as it was at the time of rendering, meaning that the state updates won't immediately reflect the latest changes within the same render cycle. This is why the following code won't update the number by the number of clicks on the button.
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 1); setNumber(number + 1); setNumber(number + 1); }}>+3</button> </> ) }
In this example, the number stays at 0, even though the button is clicked three times.
Batching
React 18 introduced automatic batching.
React groups multiple state updates into a single re-render for better performance.
Automatic batching for fewer renders in React 18
React provides a great analogy: imagine a waiter at a restaurant. The waiter doesn't deliver orders one by one to the chef. Instead, they wait for all the orders to be placed or for any changes to be made, and then pass them all to the chef at once. Additionally, the waiter can take orders from other tables while waiting. Similarly, React waits for multiple state updates before re-rendering the component, ensuring there are no "half-renders" and making the app run more efficiently.
Updater Function
To implement multiple state updates in one event, such as in the counter code shared above (where we want the counter to increment by 3 with one click), we can use an updater function.
setNumber(n => number + 1);
This function (n => n + 1)
is queued and executed after all the other code in the event handler has finished running.
Updating Objects in State
JavaScript values such as numbers, strings, and booleans are immutable. For objects, React recommends treating them as immutable as well. As mentioned earlier, React triggers a re-render when there's a change in the state. To update the state, we create a new version of it by copying the existing object.
We can use the spread syntax, ...
for this purpose. However, it's important to remember that the spread syntax creates a shallow copy, meaning it won't copy nested objects. If you need to update nested objects, you'll need to create copies down to the target objects.
Updating Arrays in State
Just like objects, arrays should be treated as immutable. Before updating the state, we should create a copy of the array.
Some array methods, such as pop
, push
, splice
, reverse
, and sort
, modify the original arrays, so we should avoid using them.
Instead, we can use methods like:
[...arr, newItem]
for adding an item,
filter()
for removing items,
map()
for replacing items.
There are other methods as well, but these are the most commonly used ones.
In React, state is used to manage and update data within components for better performance. As developers, it's important to understand how state works and how to use it effectively to fully leverage its potential in our applications.
Top comments (0)