Search code examples
reactjstypescriptsettimeoutreact-ref

Create the setTimeout in the parent and closes it in the children


I have a specific problem that is keeping me awake this whole week.

I have a parent component which has a pop-up children component. When I open the page the pop-up shows off and after 5 seconds it disappears with a setTimeout.

This pop-up has an input element in it.

I want the pop-up to disappear after 5 seconds or if I click to digit something in the input. I tried to create a timerRef to the setTimeout and closes it in the children but it didn't work.

Can you help me, please? Thanks in advance.

ParentComponent.tsx


const ParentComponent = () => {
    const [isVisible, setIsVisible] = useState(true)
    timerRef = useRef<ReturnType<typeof setTimeout>>()

    timerRef.current = setTimeout(() => {
        setIsVisible(false)
    }, 5000)

    useEffect(() => {
        return () => clearTimeout()
    })

    return (
        <div>
            <ChildrenComponent isVisible={isVisible} inputRef={timerRef} />
        </div>
    )
}

ChildrenComponent.tsx


const ChildrenComponent = ({ isVisible, inputRef}) => {
    return (
        <div className=`${isVisible ? 'display-block' : 'display-none'}`>
            <form>
                <input onClick={() => clearTimeout(inputRef.current as NodeJS.Timeout)} />
            </form>
        </div>
    )
}

Solution

  • You're setting a new timer every time the the component re-renders, aka when the state changes which happens in the timeout itself.

    timerRef.current = setTimeout(() => {
      setIsVisible(false);
    }, 5000);
    

    Instead you can put the initialization in a useEffect.

    useEffect(() => {
      if (timerRef.current) return;
    
      timerRef.current = setTimeout(() => {
        setIsVisible(false);
      }, 5000);
      return () => clearTimeout(timerRef.current);
    }, []);
    

    You should also remove the "loose" useEffect that runs on every render, this one

    useEffect(() => {
      return () => clearTimeout();
    });