DEV Community

Gakii
Gakii

Posted on

7

Functional State Update in React.

State updates in React are asynchronous which means that the state does not necessarily update instantly.

This is by design to improve the performance of React applications. As of React 18, React now batches state updates even if they were inside an event handler or a promise callback.

When you update the state in React, this will require re-rendering of your component (and potentially other components), which could be an expensive operation.

This is why React batches several state updates together and combines them into one re-rendering in order to make your app more responsive and reduce the amount of work the browser has to do.

Let us look at an example:

import React, {useState} from "react";

 function App() {    
    const [date, setDate] = useState(new Date());
    const [counter, setCounter] = useState(0);

    console.log("rendered"); // to visualise re-renders

    function handleButtonClick() {
        setDate(new Date());
        setCounter(counter + 1);
    }

    return <button onClick={handleButtonClick}>Click me</button>
}
Enter fullscreen mode Exit fullscreen mode

There are two state updates when the button is clicked. However, the component will only re-render once.

This is because React batches (merges) these two state changes and performs them at the same time. This makes your app respond faster to user interactions.

Because state updates are asynchronous, there's a caveat that we have to be aware of. For the sake of simplicity, let's say that we have the following component:

import {useState} from "react";

function App() {
    const [counter, setCounter] = useState(0);

    function handleButtonClick() {
        setCounter(counter + 1);
        setCounter(counter + 1);
    }

    return <button onClick={handleButtonClick}>Click me {counter}</button>
}
Enter fullscreen mode Exit fullscreen mode

After clicking the button once, the value of the counter will be 1 not 2.
The two state updates will be batched together, and starting with counter = 0, the first setCounter() call will set the counter to 0 + 1 = 1, but not immediately because it's asynchronous.

Then we have the following call setCounter(counter + 1), but the value of counter is still 0 because the component did not re-render yet.
So the second call will also set the state to 1.

Functional state updates

To solve this issue, React has the concept of functional state updates which is _when you pass a function to the state setter function. _

The function passed to setState receives the previous state as an argument that is used to compute the next state.
Here's an example:

setCounter((previousCounter) => {
    return previousCounter + 1;
});
Enter fullscreen mode Exit fullscreen mode

You can use a shorter version of course:

setCounter(previousCounter => previousCounter + 1);
Enter fullscreen mode Exit fullscreen mode

We give it a function definition that takes the previous value of the state and returns the new state. In this example, the new state is the previous one + 1.

Here's how you can fix the above example to add to the state twice:

import {useState} from "react";

function App() {
    const [counter, setCounter] = useState(0);

    function handleButtonClick() {
        setCounter(prevCounter => prevCounter + 1);
        setCounter(prevCounter => prevCounter + 1);
    }

    return <button onClick={handleButtonClick}>Click me {counter}</button>
}
Enter fullscreen mode Exit fullscreen mode

This will add 2 to the counter every time the button is clicked.
prevCounter => prevCounter + 1 is a function definition.
React will call this function and pass the previous value of the state as the first argument.

This means that you can name that argument whatever you choose. In this example, we used prevCounter.

Whenever the new state is computed using the previous state, it is recommended that you use functional state updates to guarantee consistency and prevent unexpected bugs.

If for example you are incrementing a counter, you should use a functional state update. On the other hand, if you're resetting a counter back to 0, you don't necessarily have to use a functional state update (as the new value is not computed using the previous state).

I hope this post was helpful. Please share and leave a comment if anything resonates with you.

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay