Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks: Why do several useState setters in an async function cause several rerenders?

This following onClick callback function will cause 1 re-render:

const handleClickSync = () => {
  // Order of setters doesn't matter - React lumps all state changes together
  // The result is one single re-rendering
  setValue("two");
  setIsCondition(true);
  setNumber(2);
};

React lumps all three state changes together and causes 1 rerender.

The following onClick callback function, however, will cause 3 re-renderings:

const handleClickAsync = () => {
  setTimeout(() => {
    // Inside of an async function (here: setTimeout) the order of setter functions matters.
    setValue("two");
    setIsCondition(true);
    setNumber(2);
  });
};

It's one re-render for every useState setter. Furthermore the order of the setters influences the values in each of these renderings.

Question: Why does the fact that I make the function async (here via setTimeout) cause the state changes to happen one after the other and thereby causing 3 re-renders. Why does React lump these state changes together if the function is synchronous to only cause one rerender?

You can play around with this CodeSandBox to experience the behavior.

like image 344
Andru Avatar asked Nov 05 '21 15:11

Andru


People also ask

Is useState setter async?

We've to use the useEffect hook to watch the state value because React's useState state setter function is async. The array we pass in as the 2nd argument of useEffect has the state or prop values we want to watch.

How does useState trigger re-render?

The useState() hook in react allows us to declare a state variable that persists during the re-render cycles. If we want to re-render the component then we can easily do so by calling the setState() function which is obtained by destructuring the array returned as a result of calling the useState() hook.

Can we use multiple useState in React?

By following this rule, you ensure that Hooks are called in the same order each time a component renders. That's what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

Is useState Hook synchronous or asynchronous?

TL;DR: useState is an asynchronous hook and it doesn't change the state immediately, it has to wait for the component to re-render. useRef is a synchronous hook that updates the state immediately and persists its value through the component's lifecycle, but it doesn't trigger a re-render.

What is usestate in react hooks?

This example renders a counter. When you click the button, it increments the value: Here, useState is a Hook (we’ll talk about what this means in a moment). We call it inside a function component to add some local state to it.

Why can’t I use async functions with React’s useeffect hook?

Because React’s useEffect hook expects a cleanup function returned from it which is called when the component unmounts. Using an async function here will cause a bug as the cleanup function will never get called. Yikes! So what do we do? Directives, simple right? Wrong!

What is the difference between React components and hooks?

Conceptually, React components have always been closer to functions. Hooks embrace functions, but without sacrificing the practical spirit of React. Hooks provide access to imperative escape hatches and don’t require you to learn complex functional or reactive programming techniques.

Can I reuse stateful behavior between React components?

(We don’t recommend rewriting your existing components overnight but you can start using Hooks in the new ones if you’d like.) React provides a few built-in Hooks like useState. You can also create your own Hooks to reuse stateful behavior between different components.


2 Answers

In react 17, if code execution starts inside of react (eg, an onClick listener or a useEffect), then react can be sure that after you've done all your state-setting, execution will return to react and it can continue from there. So for these cases, it can let code execution continue, wait for the return, and then synchronously do a single render.

But if code execution starts randomly (eg, in a setTimeout, or by resolving a promise), then code isn't going to return to react when you're done. So from react's perspective, it was quietly sleeping and then you call setState, forcing react to be like "ahhh! they're setting state! I'd better render". There are async ways that react could wait to see if you're doing anything more (eg, a timeout 0 or a microtask), but there isn't a synchronous way for react to know when you're done.

You can tell react to batch multiple changes by using unstable_batchedUpdates:

import { unstable_batchedUpdates } from "react-dom";

const handleClickAsync = () => {
  setTimeout(() => {
    unstable_batchedUpdates(() => {
      setValue("two");
      setIsCondition(true);
      setNumber(2);    
    });
  });
};

In version 18 this isn't necessary, since the changes they've made to rendering for concurrent rendering make batching work for all cases.

like image 56
Nicholas Tower Avatar answered Oct 23 '22 13:10

Nicholas Tower


Right now react only batches sync setStates inside event handlers. But in react 18 it will be available in setTimeout, useEffects etc Here is excellent explanation from Dan https://github.com/reactwg/react-18/discussions/21

like image 17
jkaczmarkiewicz Avatar answered Oct 23 '22 13:10

jkaczmarkiewicz