简体   繁体   中英

How to Slow Down React State Update Intentionally - Batch Updates

Occasionally I may want to unmount and remount a component with new data inside it. This could look like:

setAllPosts(undefined);
setAllPosts(newArrayOfPosts);

Because React batches state changes, depending on where the newArrayOfPosts is coming from, the state won't change. I've been able to hack a solution with a setTimeout() of 1 second and then filling in setAllPosts(), but this feels so wrong.

Is there a best practice way to tell React to slow down for a moment? or maybe to not batch update this particular state change?

PS I know there are better ways to do this, but I am working inside a third party environment and am pretty limited to what I have access to.

Once react 18 is available (it's currently a release-candidate) there will be a function that can force updates to not be batched: flushSync

import { flushSync } from 'react-dom';

flushSync(() => {
  setAllPosts(undefined);
});
flushSync(() => {
  setAllPosts(newArrayOfPosts);
});

Until then, you may need to do the setTimeout approach (though it doesn't need to be a whole second).

PS I know there are better ways to do this, but I am working inside a third party environment and am pretty limited to what I have access to.

Yeah, if you can do something else that would probably be better. Most of the time, if you want to deliberately unmount/remount a component, that is best achieved by using a key which you change when you want the remount to happen.

const [key, setKey] = useState(0);
const [allPosts, setAllPosts] = useState([]);

// ...
setKey(prev => prev + 1);
setAllPosts(newArrayOfPosts);

// ...
return (
   <SomeComponent key={key} posts={allPosts} />
)

This is how you can do it -

import { flushSync } from 'react-dom';

const handleClick = () => {
  flushSync(() => {
    setAllPosts(undefined);
  // react will create a re-render here
  });
  
  flushSync(() => {
    setAllPosts(newArrayOfPosts);
  // react will create a re-render here
  });
};

This is used to un-batch the react states. This is just a single way of doing it. The other way could be to use setTimeout . Please note that with version 18 of react, state updates within setTimeouts are also being batched - this is known as Automatic Batching, but we still can achieve this by using different setTimeouts -

const handleClick = () => {
  setTimeout(() => {
    setAllPosts(undefined);
  // react will create a re-render here
  }, 10);
  
  setTimeout(() => {
    setAllPosts(newArrayOfPosts);
  // react will create a re-render here
  },20);
};

Just make sure to keep a time difference to rule out the batching done by React.

Occasionally I may want to unmount and remount a component with new data inside it.

It sounds like this use-case calls for a useEffect() with a dependency based on something you care about, like another piece of state or prop being provided to this component.

useEffect(() => {
   setAllPosts(newArrayOfPosts);
}, [shouldUpdate]);

I've even seen examples of people triggered useEffect() with a dependency of a piece of state called count or renderCount . Not sure if this is necessarily best practice but it's one way to go about things.

const [count, setCount] = useState(0);
const [allPosts, setAllPosts] = useState([]);

useEffect(() => {
   setAllPosts(props.values);
}, [count]);

const handleChange = () => {
   setCount(prevCount => prevCount + 1); // This will trigger your useEffect when handleChange() in invoked
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM