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.
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)
}, [])
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()
}