skip to Main Content

On this answer by Dan Abramov here on SO, I’ve found out the following:

Does React keep the order for state updates?

Currently (React 16 and earlier), only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.

He also mentions that in this Github issue:

https://github.com/facebook/react/issues/10231#issuecomment-316644950

In current release, they will be batched together if you are inside a React event handler. React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.

But the fact is that this snippet seems to prove that updates for multiple setState calls inside a useEffect() are batched.

QUESTION

Does React also always batch updates for multiple setState() calls inside useEffect ? Where else does it do that ?

NOTE: According to his answers, on next major release (probably v17) React will batch everywhere by default.

SNIPPET: batch updates inside a useEffect() with multiple setState() calls

function App() {

  console.log('Rendering app...');
  
  const [myState,setMyState] = React.useState(0);
  const [booleanState, setBooleanState] = React.useState(false);
  
  console.log('myState: ' + myState);
  console.log('booleanState: ' + booleanState);
  
  React.useEffect(()=>{
    console.log('Inside useEffect...');
    setMyState(1);
    setMyState((prevState) => prevState +1);
    setMyState(3);
    setMyState(4);
    setMyState(5);
    setBooleanState(true);
  },[]);

  return(
    <div>App - Check out my console!</div>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

3

Answers


  1. If state updates happen directly, React will batch your updates.

    Batched:

    export default () => { 
       const [a, setA] = React.useState(0); 
       const [b, setB] = React.useState(0);
    
       useEffect(() => {
        setA(1); setB(1);
       },[]);
    
       return (  
        <div> 
          <p>A: {a}</p> 
          <p>B: {b}</p> 
        </div> ); 
    };
    

    Clicking this button will re-render the component only once.

    Non-Batched:

    export default () => { 
      const [a, setA] = React.useState(0); 
      const [b, setB] = React.useState(0);
    
       useEffect(() => { 
        setTimeout(() => { setA(1); setB(1); }, 1000); }
       , []);
    
       return ( 
        <div> 
         <p>A: {a}</p> 
         <p>B: {b}</p> 
        </div> ); 
    }; 
    
    Login or Signup to reply.
  2. Nice question. Here is additional info to complete @FranklinOcean answer.

    2021 update: upcoming changes in React 18

    Please see Dan Abramov update concerning this topic in React 18, which is the one that adds automatic batching: https://github.com/reactwg/react-18/discussions/21

    Answer for current version of react, which is 17.0.2, and below, as of 2021.

    Based on the following codesandbox:


    Batched setStuff calls:

    • Inline the component function block synchronously (I think it is equivalent to having an effect running before the other effects and without dependencies)
    • In a useEffect block synchronously
    • In a synthetic event handler synchronously (managed by react, such as onClick={handlerFunction})

    Non batched calls that will trigger a re-render each time:

    • Any asynchronous code (promise/async function in any of the above use cases)
    • Non synthetic event (event managed outside react lib)
    • That includes XHR or other networks callbacks

    I’ll try re-run the sandbox with future versions of react to see how it goes!

    Login or Signup to reply.
  3. just gonna drop in a "hack" that worked for me

      const [dishes, dishesSet] = React.useState(state)
      const [isConnectedToDB, isConnectedToDBSet] = React.useState(false)
    
      React.useEffect(() => {
        const databaseDishesRef = firebase.database().ref(process.env.FIREBASE_DISHES_REF)
        databaseDishesRef.on('value', (snapshot) => {
          const value = snapshot.val()
          // isConnectedToDBSet(true) // this was not working
          dishesSet(() => {
            isConnectedToDBSet(true) // had to move it in here to batch the state updates
    
            return Object.entries(value).map(([, dish]) => dish)
          })
        })
    
        return () => {
          databaseDishesRef.off('value')
        }
      }, [])
    

    The alternative would have been to wrote a ‘Reac.useEffectfor theisConnectedToDB` and then trigger that on EVERY dish update..

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search