I am trying to convert a fairly simple React class component to functional component. Here on document click I want to toggle the isShown
boolean value between true
and false
. But each time onClick
is fired, the value isShown
is always false
, even though it was previously toggled to true
. I do not understand what is going on here and how to fix it?
import React, { useEffect, useState } from 'react';
export default function Popover({}: any) {
const [isShown, setIsShown] = useState(false);
useEffect(() => {
document!.addEventListener('click', onClick);
}, []);
function onClick(e: Event) {
console.log(isShown);
setIsShown(!isShown);
}
return (<div></div>);
}
Let's focus on the useEffect
. In there you added a listener that the callback function is onClick
. The onClick
function is declared with isShown: false
at the first. So the onClick
will be something like that:
function onClick(e: Event) {
console.log(false); // isShown is `false` and will be logged
setIsShown(!false); // isShown will be set to `true`
}
So when you click on the button, the isShown
value will be changed to true
but the onClick
function won't be declared again for the listener in the useEffect and the callback function of the event listener is the one I explained before. Now, you know the problem and you have two solutions:
onClick
funtion to not to depend on the isShown
state like this: function onClick(e: Event) {
setIsShown(previousValue => !previousValue);
}
onClick
changes. For this solution, to avoid the infinit rerenders, you need to memoize the onClick
function or move the function in the body of the useEffect: const onClick = useCallback((e: Event) => {
console.log(isShown);
setIsShown(!isShown);
}, [isShown]);
useEffect(() => {
document!.addEventListener('click', onClick);
return () => document!.removeEventListener('click', onClick);
}, [onClick]);
OR
useEffect(() => {
function onClick(e: Event) {
console.log(isShown);
setIsShown(!isShown);
}
document!.addEventListener('click', onClick);
return () => document!.removeEventListener('click', onClick);
}, [isShown]);