Reputation: 15617
We have migrated to 'React Functional Components' instead of 'Class based Component'. I cannot find the substitute logic for setState
callback 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?
Upvotes: 2
Views: 2621
Reputation: 2036
I'll tell you that it works pretty much in the same way as this.setState
, you just a pass a callback function which takes previous state as a parameter and returns new state(docs)
const Example = () => {
const [openDoor, setOpenDoor] = useState(false);
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
}
return(...);
}
In order for you know when it changes you can use useEffect
callback, which's gonna be called each time something changes in the dependencies array(docs)
const Example = () => {
const [openDoor, setOpenDoor] = useState(false);
useEffect(() => {
console.log('openDoor changed!', openDoor)
}, [openDoor])
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
}
return(...);
}
Upvotes: 1
Reputation: 1950
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:
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.
Upvotes: 2
Reputation: 20821
import React, { useState, useEffect } from "react";
const Example = () => {
const [openDoor, setOpenDoor] = useState(false);
const toggleOpenDoor = () => {
setOpenDoor(!openDoor);
};
useEffect(() => {
console.log(openDoor);
if (openDoor) {
setTimeout(() => setOpenDoor(!openDoor), 1500);
}
}, [openDoor]);
return (
<>
<button onClick={toggleOpenDoor}>Toggle</button>
<p>{`openDoor: ${openDoor}`}</p>
</>
);
};
export default Example;
Upvotes: 1
Reputation: 53874
You should just using setTimeout
within useEffect
callback:
const App = () => {
const [openDoor, setOpenDoor] = useState(false);
const toggle = () => setOpenDoor(prevOpen => !prevOpen);
useEffect(() => {
const id = setTimeout(() => toggle(), 1000);
return () => clearTimeout(id);
}, [openDoor]);
return <Container>isOpen: {String(openDoor)}</Container>;
};
Upvotes: 1
Reputation: 91
You can use useEffect
hook to achieve this.
setOpenDoor(!openDoor);
useEffect(() => {
// Here your next setState function
}, [openDoor]);
For more information on hooks please check out https://reactjs.org/docs/hooks-effect.html
Upvotes: 1
Reputation: 11001
You can use useEffect hook to see when the state change happend.
useEffect(() => {
// do something
console.log('openDoor change', openDoor)
}, [openDoor]);
Upvotes: 2