Can you setState in useEffect?
Can you and should you useState
inside a useEffect hook
?
The answer is yes, you can set state inside useEffect
, but you shouldn’t as it can cause additional renders or an infinite loop error.
Let’s look at some bad examples of using useState
inside useEffect
.
Problem #1: Using useState
to communicate with the parent component
Here’s a common practice that I’ve seen in the work place. Where a child component may accept a callback functions.
And depending on the state the child component, it may do the side effect such as onOpen()
or onClose()
.
export default function ChildComp({onOpen, onClose }) {
const { isOpen, setOpen } = useState(false);
useEffect(() => {
if (isOpen) {
onOpen();
} else {
onClose();
}
}, [isOpen])
return (
<div className="App">
<button
onClick={() => setOpen(!isOpen)}>
Toggle View
</button>
</div>
);
}
One of the problems with this method is that you may be causing additional renders due to the state change from the child to parent component.
A better solution, is to move these kind of functions closer to the event handlers when these changes happen. Here’s an example of better code.
export default function App({onOpen, onClose}) {
const { isOpen, setOpen } = useState(false);
function handleToggleView() {
// Forward seeing the next value is `isOpen`
const nextIsOpen = !isOpen;
// Change state value
setOpen(!isOpen);
// Use `nextIsOpen` to handle side effects
if (nextIsOpen) {
onOpen();
} else {
onClose();
}
}
return (
<div className="App">
<button
onClick={handleToggleView}>
Toggle View
</button>
</div>
);
}
The solution now removes the use of useEffect
and is forward seeing the next value of the state variable isOpen
by assigning it to a variable called nextIsOpen
.
The handler functions of onOpen()
and onClose()
are now happening within the event handler.
Problem #2: Infinite loops
Another problem with setting state within a useEffect()
hook is that you can cause an infinite loop.
const { isOpen, setOpen } = useState(false);
useEffect(() => {
setOpen(!isOpen)
});
This can cause some real issues, like crashing your React application.
In the case that you need to set state inside a useEffect
hook, add the array dependency. Let’s look at some examples:
const { isOpen, setOpen } = useState(false);
// Happens once
useEffect(() => {
setOpen(!isOpen)
}, []);
// Happens when `isOpen` is modified
useEffect(() => {
if (!isOpen) {
setOpen(true)
}
}, [isOpen]);
A better solution than the code above is to just move it closer to the event handler as possible. Whatever decides that sets isOpen
to equal true
, that’s where that logic should live.
The point of the article is that you really don’t need useEffect
90% of the time.
I like to tweet about React and post helpful code snippets. Follow me there if you would like some too!