From 1d62feaf7a1c4f2ab1f94f279e402541a61cc894 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 28 May 2026 17:15:12 -0300 Subject: [PATCH] fix(translator): suppress react-hooks/set-state-in-effect in deep-link sync useEffects (follow-up GAP-NOVO-4/5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The useRef-based pattern is required so that a manual user close while forceOpen stays true does not immediately re-open the accordion on the next render (see test 'toggle closes accordion again'). The setState calls inside the false→true guard are an intentional sync of an external deep-link prop into local component state — a valid use case the react-hooks/set-state-in-effect heuristic does not recognize, so suppress it with an inline comment explaining the rationale. Clarified the comment on the prevForceOpen useRef to point at the regression it prevents. --- .../components/advanced/CompressionPreviewAccordion.tsx | 7 ++++--- .../components/advanced/StreamTransformerAccordion.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion.tsx b/src/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion.tsx index 0f9dfe7006..42ad2dc869 100644 --- a/src/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion.tsx +++ b/src/app/(dashboard)/dashboard/translator/components/advanced/CompressionPreviewAccordion.tsx @@ -216,16 +216,17 @@ export default function CompressionPreviewAccordion({ // Lazy-render guard (D7): track whether the accordion has ever been opened. const [hasOpened, setHasOpened] = useState(forceOpen); const [open, setOpen] = useState(forceOpen); - // Track previous forceOpen value to detect false→true transitions only. + // Track previous forceOpen so the effect only reacts to false→true transitions. + // Without this, a manual close while forceOpen stays true would re-open the accordion + // on the very next render (the test "toggle closes accordion again" guards this). const prevForceOpen = useRef(forceOpen); // Sync forceOpen changes from parent after mount (deep-link / back-forward navigation). - // Only reacts to a false→true transition so a manual close is not overridden while - // forceOpen remains true (e.g. user toggles closed after a deep-link open). useEffect(() => { const prev = prevForceOpen.current; prevForceOpen.current = Boolean(forceOpen); if (!prev && forceOpen) { + // eslint-disable-next-line react-hooks/set-state-in-effect -- syncing deep-link prop into local state setOpen(true); setHasOpened(true); onOpenChange?.(true); diff --git a/src/app/(dashboard)/dashboard/translator/components/advanced/StreamTransformerAccordion.tsx b/src/app/(dashboard)/dashboard/translator/components/advanced/StreamTransformerAccordion.tsx index d1449b4607..63a7aca1e8 100644 --- a/src/app/(dashboard)/dashboard/translator/components/advanced/StreamTransformerAccordion.tsx +++ b/src/app/(dashboard)/dashboard/translator/components/advanced/StreamTransformerAccordion.tsx @@ -140,16 +140,17 @@ export default function StreamTransformerAccordion({ const [open, setOpen] = useState(Boolean(forceOpen)); // D7 lazy-render guard: once mounted, keep content in DOM. const [hasOpened, setHasOpened] = useState(Boolean(forceOpen)); - // Track previous forceOpen value to detect false→true transitions only. + // Track previous forceOpen so the effect only reacts to false→true transitions. + // Without this, a manual close while forceOpen stays true would re-open the accordion + // on the very next render. const prevForceOpen = useRef(Boolean(forceOpen)); // Sync forceOpen changes from parent after mount (deep-link / back-forward navigation). - // Only reacts to a false→true transition so a manual close is not overridden while - // forceOpen remains true (e.g. user toggles closed after a deep-link open). useEffect(() => { const prev = prevForceOpen.current; prevForceOpen.current = Boolean(forceOpen); if (!prev && forceOpen) { + // eslint-disable-next-line react-hooks/set-state-in-effect -- syncing deep-link prop into local state setOpen(true); setHasOpened(true); onOpenChange?.(true);