Home > Software engineering >  React setState takes 200ms
React setState takes 200ms

Time:06-18

I'm listening to a websocket which triggers a callback once per second:

setState1 happens every second, setState2 also happens every second after 10 seconds and allows LargeChildComponent to display.

I found that updating this state was taking over 200ms.

Slow setState console logs and Performance tab

const myWebsocketCallback = async(newData) => {
  dataRef.current = [...dataRef.current, newData]

  const start = performance.now()
  setDataState(dataRef.current)
  console.log('setState', performance.now() - start)

  doWorkWithNewData()
}

So setting state was slow initially, and is extra slow when LargeChildComponent is rendering.


I found that moving a large JSON object from a state initialization to a init useEffect() reduced that time greatly.

- const [myBigData, setMyBigData] = useState(VERY_LARGE_JSON_OBJECT)

  const [myBigData, setMyBigData] = useState()
  useEffect(() => {
    setMyBigData(VERY_LARGE_JSON_OBJECT)
  }, [])

Faster setState console logs and Performance tab

Is default state value checked every render? Should I be initializing state in a useEffect(myFunc, [])?


The above change alone made a MASSIVE improvement.

However, that setState() is still taking 80ms , AND it seems to be dependent on the size of the render (child components).

I was under the impression that setState() is an async task and should happen quickly regardless of any components which depend on it.

Why is it still taking 80ms to update state?

Is it due to being inside a callback function? Does state actually wait for children to render?

CodePudding user response:

anytime you call setState outside of a React lifecycle method (React event handler or render function or effect) then it tends to block while React re-renders everything. In React 18 they are making changes to the way state changes and renders are batched.

You can either:

Update to React 18 createRoot to get automatic batching


import ReactDOM from "react-dom";
import App from 'App';

const container = document.getElementById('root');

// Create a root.
const root = ReactDOM.createRoot(container);

// Initial render
root.render(<App name="Saeloun blog" />);

// During an update, there is no need to pass the container again
root.render(<App name="Saeloun testimonials" />);

or use the unstable_batchUpdate method in React < 18

import { unstable_batchedUpdates } from 'react-dom'

const myWebsocketCallback = async(newData) => {
  dataRef.current = [...dataRef.current, newData]

  const start = performance.now()
  unstable_batchUpdates(() => {
    setDataState(dataRef.current)
  })
  console.log('setState', performance.now() - start)

  doWorkWithNewData()
}


  • Related