简体   繁体   中英

React - in a functional component, using hooks, can I execute a piece of code once after one setState() successfully changes state?

setState updates state asynchronously. It's my understanding that, when using a class component, you can do something like this to ensure certain code is executed after one setState updates state:

setState({color: red}, callbackThatExecutesAfterStateIsChanged);

I'm using a functional component & hooks. I'm aware, here, useEffect() 's callback will execute everytime after color state changes and on initial execution.

useEffect(callback, [color]);

How can I replicate similar behaviour as the class component example - that is, to execute a chunk of code once after one setState() successfully changes state and not on initial execution ?

If you ask me, there is no safe way to do this with hooks. The problem is that you both have to read and set an initialized state in order to ignore the first update:

const takeFirstUpdate = (callback, deps) => {
  const [initialized, setInitialized] = useState(false);
  const [wasTriggered, setWasTriggered] = useState(false);

  useEffect(() => {
    if (!initialized) {
      setInitialized(true);
      return;
    }
    if (wasTriggered) {
      return;
    }
    callback();
    setWasTriggered(true);
  }, [initialized, wasTriggered]);
};

While the hook looks like it works, it will trigger itself again by calling setInitialized(true) in the beginning, thus also triggering the callback.

You could remove the initialized value from the deps array and the hook would work for now - however this would cause an exhaustive-deps linting error . The hook might break in the future as it is not an "official" usage of the hooks api, eg with updates on the concurrent rendering feature that the React team is working on.

The solution below feels hacky. If there's no better alternative, I'm tempted to refactor my component into a class component to make use of the easy way class components allow you to execute code once state has been updated.

Anyway, my current solution is:

The useRef(arg) hook returns an object who's .current property is set to the value of arg. This object persists throughout the React lifecycle. ( Docs ). With this, we can record how many times the useEffect 's callback has executed and use this info to stop code inside the callback from executing on initial execution and for a second time. For example:

initialExecution = useRef(true);
[color, setColor] = useState("red");

useEffect(() => {
  setColor("blue");
});

useEffect(() => {
  if (initialExecution.current) {
    initialExecution.current = false;
    return;
  }
  //code that executes when color is updated.
}, [color]);

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