I have been trying to use setTimeout in react to make a Popper component disappear off of the user screen. The Popper is set to appear after the user clicks a button. The visibility of the Popper component is tied to the "popperOpen" state below.
I have tried putting the setTimeout method inside of the callback function of the relevant button and also in a useEffect with no dependency array (both shown below). In the former, the Popper disappears immediately. In the latter, the Popper never disappears. Not shown below, but I have also tried useEffect with "popperOpen" in the dependency array.
What would be the proper way to get the desired functionality? And more fundamentally, how should I think about setTimeout in the context of react? (i.e., constant re-renders).
setTimeout in useCallback
const shareRef = useRef()
const [popperOpen, setPopperOpen] = useState(false);
const [anchor, setAnchor] = useState(shareRef.current)
useEffect(() => {
setAnchor(shareRef.current);
}, [shareRef])
const onShare = useCallback(() => {
const id = window.location.pathname.split('/')[2]
navigator.clipboard.writeText(window.location.origin "/share/" id)
setPopperOpen(true)
console.log("popper open")
const timeout = setTimeout(()=>{setPopperOpen(false)},1000)
console.log(timeout)
return(()=>{clearTimeout(timeout)})
},[setPopperOpen])
return(
<Button startIcon={<BiShare />} onClick={onShare} ref={shareRef}>
Share
</Button>
<Popper anchorEl={anchor} open={popperOpen} placement='bottom'>
Pressed Button!
</Popper>
)
setTimeout in useEffect
const shareRef = useRef()
const [popperOpen, setPopperOpen] = useState(false);
const [anchor, setAnchor] = useState(shareRef.current)
useEffect(() => {
setAnchor(shareRef.current);
}, [shareRef])
const onShare = useCallback(() => {
const id = window.location.pathname.split('/')[2]
navigator.clipboard.writeText(window.location.origin "/share/" id)
setPopperOpen(true)
console.log("popper open")
},[setPopperOpen])
useEffect(()=>{
const timeout = setTimeout(()=>{setPopperOpen(false)},1000)
console.log(timeout)
return(()=>{clearTimeout(timeout)})
},[])
return(
<Button startIcon={<BiShare />} onClick={onShare} ref={shareRef}>
Share
</Button>
<Popper anchorEl={anchor} open={popperOpen} placement='bottom'>
Pressed Button!
</Popper>
)
CodePudding user response:
This is a problem I've run into often when trying to use state setters as callbacks to other asynchronous/event-driven functions like addEventListener
. My understanding of the problem is that due to the nested functions, a closure is being created around the initial value of setPopperOpen
, so when that value is invoked later on, it is no longer equal to the latest setPopperOpen
(because that variable is reassigned upon each re-render).
My solution has been to create a variable outside of the scope of your component, at the level of the module, for example let _setPopperOpen
. Then, inside of your component, set _setPopperOpen = setPopperOpen
(not in a useEffect
or anything, just at the top level of your component). Finally, inside of your setTimeout
, invoke the function _setPopperOpen
, rather than setPopperOpen