From 2a28fb0553aabcacb8283057ccc11edc6cf1879e Mon Sep 17 00:00:00 2001 From: James Herdman Date: Thu, 23 May 2024 13:56:32 -0400 Subject: [PATCH] fix: Better Popper Timeout Tracking We noticed that Popper was sometimes closing the dorpdown inaccurately. This was pinned down to timeouts being incorrectly cleared, and thefore sometimes running when they ought not to. Careful examination of the code revealed that they weren't being tracked very well, and thus stomped on or lost. This approach leverages a ref to track the value to avoid loss during re-renders, and to also avoid unnecessary re-renders. --- src/Popper/Popper.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Popper/Popper.tsx b/src/Popper/Popper.tsx index 8d18eb3e4..e163787d5 100644 --- a/src/Popper/Popper.tsx +++ b/src/Popper/Popper.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Manager, Reference, Popper as ReactPopperPopUp } from "react-popper"; import { useTranslation } from "react-i18next"; import { PopperArrow } from "../utils"; @@ -49,20 +49,28 @@ const Popper: React.FC = React.forwardRef( }, popperRef ) => { - let timeoutID; + // We're going to manage the ID of the timeout in a ref so that we can examine + // it without causing a re-render. Note that "0" will denote "no jobs running", + // whereas positive values are the ID of the running job. + const timeoutId = useRef(0); + + const resetTimeoutId = () => { + clearTimeout(timeoutId.current); + timeoutId.current = 0; + }; const [isOpen, setIsOpen] = useState(defaultOpen); const conditionallyApplyDelay = (fnc: () => void, delay: number, skipDelay = true) => { if (!skipDelay) { - timeoutID = setTimeout(fnc, delay); + timeoutId.current = setTimeout(fnc, delay); } else { fnc(); } }; const setPopUpState = (nextIsOpenState: boolean, skipDelay: boolean) => { - clearTimeout(timeoutID); + resetTimeoutId(); conditionallyApplyDelay(() => setIsOpen(nextIsOpenState), nextIsOpenState ? showDelay : hideDelay, skipDelay); }; @@ -85,7 +93,7 @@ const Popper: React.FC = React.forwardRef( const cleanup = () => { document.removeEventListener("keydown", handleKeyDown); - clearTimeout(timeoutID); + resetTimeoutId(); }; return cleanup; }, []);