Home > Net >  Using setTimeout in React functional component in useCallback and useEffect
Using setTimeout in React functional component in useCallback and useEffect

Time:09-29

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

  • Related