Ad

React - Substitute For `setState` Callback In Functional Components?

- 1 answer

We have migrated to 'React Functional Components' instead of 'Class based Component'. I cannot find the substitute logic for setStatecallback function. I.e, I have a functional component with state, and I want to create an event handler function that mutates the state multiple times in sequence, the caveat being that I dont know the current value of state (it may be true/false). The following example may make more sense.

const Example = () => {
  const [ openDoor, setOpenDoor ] = useState(false); 
  // the following handler should swich 'openDoor' state to inverse of
  // current state value. Then after setTimeout duration, inverse it again
  const toggleOpenDoor = () => {
  setOpenDoor(!openDoor);
  // within setTimeout below, '!openDoor' does not work because it still
  // receives the same value as above because of async nature of 
  // state updates
  setTimeout(() => setOpenDoor(!openDoor), 500)
  }
  return(...);
}

In class based components, we had callback argument which would update state after previous update. How do I achieve the same in the above functional component using state hook?

Ad

Answer

I wonder if useEffect is the best solution. Specially when calling setTimeout within useEffect is going to cause an infinite loop since every time we call setOpenDoor, the app renders and then useEffect is called calling again a setTimeOut that will call a setOpenDoor function... Graphically:

setTimeout -> setOpenDoor -> useEffect -> setTimeout -> ... hell  

Of course you could use an if statement wihin useEffect the same way that @ksav suggested but that does not accomplish one requirement of @Kayote:

I dont know the current value of state (it may be true/false)

Here is a solution that works without useEffect and accomplish the requirement stated above:

Code working in codesandbox

There, see the importance of this piece of code:

const toggleOpenDoor = () => {
  setOpenDoor(!openDoor);
  setTimeout(() => setOpenDoor(openDoor => !openDoor), 500);
};

Since we are using setTimeout, we need to pass callback to setOpenDoor instead of the updated state. This is because we want to send the 'current' state. If we sent the new state instead, by the time that setTimeOut processes that state, it will have changed (because we did it before setTimeOut executes its callback with setOpenDoor(!openDoor);) and no changes will be made.

Ad
source: stackoverflow.com
Ad