From 0a7c973518fbf29071a57822c7e6c817f5599b90 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 24 Mar 2026 11:22:56 -0600 Subject: [PATCH] Improve search effect --- web/src/hooks/use-overlay-state.tsx | 64 +++++++++++++++++++---------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/web/src/hooks/use-overlay-state.tsx b/web/src/hooks/use-overlay-state.tsx index b2ef9d2f1..8efe6b0f2 100644 --- a/web/src/hooks/use-overlay-state.tsx +++ b/web/src/hooks/use-overlay-state.tsx @@ -1,4 +1,11 @@ -import { useCallback, useContext, useEffect, useMemo, useRef } from "react"; +import { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { useLocation, useNavigate, useSearchParams } from "react-router-dom"; import { usePersistence } from "./use-persistence"; import { useUserPersistence } from "./use-user-persistence"; @@ -200,36 +207,51 @@ export function useSearchEffect( const location = useLocation(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); + const [pendingRemoval, setPendingRemoval] = useState(false); + const processedRef = useRef(null); - const param = useMemo(() => { - const param = searchParams.get(key); - - if (!param) { - return undefined; - } - - return [key, decodeURIComponent(param)]; - }, [searchParams, key]); + const currentParam = searchParams.get(key); + // Process the param via callback (once per unique param value) useEffect(() => { - if (!param) { + if (currentParam == null || currentParam === processedRef.current) { return; } - const remove = callback(param[1]); + const decoded = decodeURIComponent(currentParam); + const shouldRemove = callback(decoded); - if (remove) { - navigate(location.pathname + location.hash, { - state: location.state, - replace: true, - }); + if (shouldRemove) { + processedRef.current = currentParam; + setPendingRemoval(true); } + }, [currentParam, callback, key]); + + // Remove the search param in a separate render cycle so that any state + // changes from the callback (e.g., overlay state navigations) are already + // reflected in location.state before we navigate to strip the param. + useEffect(() => { + if (!pendingRemoval) { + return; + } + + setPendingRemoval(false); + navigate(location.pathname + location.hash, { + state: location.state, + replace: true, + }); }, [ - param, - location.state, + pendingRemoval, + navigate, location.pathname, location.hash, - callback, - navigate, + location.state, ]); + + // Reset tracking when param is removed from the URL + useEffect(() => { + if (currentParam == null) { + processedRef.current = null; + } + }, [currentParam]); }