From 03a57017988283c691eca67e74b616f61a0935ab Mon Sep 17 00:00:00 2001 From: TerryM Date: Mon, 8 Jun 2026 01:18:26 +0800 Subject: [PATCH] fix(header): publish page title in useLayoutEffect to avoid stale frame on lang switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selecting Bahasa Melayu (or Vietnamese, or any locale whose nav width crosses the inline/burger threshold) showed a one-frame stutter: t=0 cn render: inline nav, title "全部资料" t=63 ms render: burger nav, **title still "全部资料"** (stale) t=78 ms render: burger nav, title "Semua aset" The stale title frame came from useSetPageTitle running its setTitle in a useEffect — that fires after the commit, so the first render after a lang change still saw the previous page's published title. Combined with the inline -> burger swap, the two-step update read as the flicker the user reported. Switching to useLayoutEffect runs setTitle synchronously between commit and paint, so the header renders the new title in the same frame as the new locale. Measured a single cn -> ms transition in the browser: the intermediate stale-title frame is gone, opacity stays at 1 throughout. --- src/components/PageTitleContext.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/PageTitleContext.tsx b/src/components/PageTitleContext.tsx index 53e15ab..b7ed6c6 100644 --- a/src/components/PageTitleContext.tsx +++ b/src/components/PageTitleContext.tsx @@ -1,7 +1,7 @@ import { createContext, useContext, - useEffect, + useLayoutEffect, useState, type PropsWithChildren, } from "react"; @@ -31,10 +31,16 @@ export function usePageTitle(): string | null { return useContext(PageTitleContext)?.title ?? null; } -/** Publish the current page's title; clears it again when the page unmounts. */ +/** + * Publish the current page's title; clears it again when the page unmounts. + * Uses useLayoutEffect so the title updates synchronously with the page render + * — otherwise switching language (e.g. cn -> ms) showed the previous title for + * one paint while the post-commit useEffect was still pending, which read as a + * flicker in the header. + */ export function useSetPageTitle(title: string | null): void { const setTitle = useContext(PageTitleContext)?.setTitle; - useEffect(() => { + useLayoutEffect(() => { setTitle?.(title); return () => setTitle?.(null); }, [setTitle, title]);