Home > Net >  Is using setTimeout in order to make sure function is called after useEffect bad?
Is using setTimeout in order to make sure function is called after useEffect bad?

Time:11-11

So let's say I have this code:

const [test, setTest] = useState();
const [test2, setTest2] = useState();

useEffect(() => {
  setTest2(undefined);
}, [test]);

const calledFunction => () {
   setTest(whatever);
   setTest2(thisIsWhatIwant);
}

return (
    <>
    {test2}
    <button onClick={() => calledFunction}></button>
    </>
);

After all of this code, I will get undefined in test2 even if I want to have "thisIsWhatIwant". So there is a small hack to achieve this:

const [test, setTest] = useState();
const [test2, setTest2] = useState();

useEffect(() => {
  setTest2(undefined);
}, [test]);

const calledFunction = () => {
   setTest(whatever);
   setTimeout(() => setTest2(thisIsWhatIwant), 1);
}

This will work because setTimeout will push setTest2 to the end of the stack (after the useEffect).

Is this bad practice? If so, is there any way to achieve what I want in a cleaner way?

Thanks.

//edit: calledFunction is called on click, on another button

CodePudding user response:

The ambiguity of this question makes this hard to answer. I'll assume the following scenario:

You want to reset test2 if test is changed except in some special circumstance.

You could create a custom setter instead of using a useEffect callback.

const [test, _setTest] = useState();
const [test2, setTest2] = useState();

const setTest = useCallback((newTest) => {
  _setTest(newTest);
  setTest2(undefined);
}, []);

const calledFunction = () => {
  _setTest(whatever);
  setTest2(thisIsWhatIwant);
}

return (
  <>
    {test2}
    <button onClick={() => calledFunction}></button>
  </>
);

In the above code we've defined the setter setTest to update both test and reset test2. "normal" code will be calling the setTest function, which closely follows your previous behaviour.

However in your special circumstance you can use _setTest which only updates the test without resetting test2.

Note there are two important differences:

  1. The custom setter is not called on component mount.
  2. If the value passed to setTest has the same identity as the previous value, then the useState callback will not be called and test2 is never reset. Whereas with this custom setter test2 will be reset regardless.

These differences in behaviour might or might not be relevant depending on the context.

CodePudding user response:

Another way to do this could be using a ref which tells you that you want to skip your code in this effect.

const [test, setTest] = useState();
const [test2, setTest2] = useState();
const skipSetter = useRef(false);

useEffect(() => {
  if(skipSetter.current){
  setTest2(undefined);
  }
  skipSetter.current = false;
}, [test]);

const calledFunction => () {
   skipSetter.current = true;
   setTest(whatever);
   setTest2(thisIsWhatIwant);
}

The primary difference between this and your current approach is that there won't be an extra rerender, which happens in your implementation because of a setState inside the setTimeout callback.

Note: You will have to be careful with the value of ref variable though and ensure you are setting it correctly where applicable.

  • Related