fix(theme): guard ThemeSessionBridge sync with hasSynced ref
The dep-only fix wasn't enough: better-auth's useSession (nanostores) can re-emit on focus / store refresh, transitioning session.user.id through undefined and back. Each transition re-fires the effect, which then calls setTheme(session.user.theme) with the stale cached value and reverts the user's selection. Track "have we synced this tab session" with a ref. After the first successful sync, no subsequent effect fire can revert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
10c68657a7
commit
f32403ee35
1 changed files with 9 additions and 7 deletions
|
|
@ -10,7 +10,7 @@
|
|||
* Renders nothing.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useSession } from '@/lib/auth-client';
|
||||
|
||||
|
|
@ -19,8 +19,15 @@ type SessionUserWithTheme = { theme?: 'light' | 'dark' | 'system' | string };
|
|||
export function ThemeSessionBridge() {
|
||||
const { data: session } = useSession();
|
||||
const { theme, setTheme } = useTheme();
|
||||
// Sync server → client EXACTLY ONCE per tab session. better-auth's
|
||||
// useSession (nanostores) can re-emit on focus / store refresh, which would
|
||||
// otherwise call setTheme with the stale cached session.user.theme and
|
||||
// revert the user's just-made selection. The PUT to /api/me/theme does NOT
|
||||
// refresh the cached session, so any post-selection re-fire would clobber.
|
||||
const hasSynced = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSynced.current) return;
|
||||
if (!session?.user) return;
|
||||
const serverTheme = (session.user as SessionUserWithTheme).theme;
|
||||
// Only enforce explicit choices. 'system' is the column default and may
|
||||
|
|
@ -32,12 +39,7 @@ export function ThemeSessionBridge() {
|
|||
setTheme(serverTheme);
|
||||
}
|
||||
}
|
||||
// Deps intentionally exclude `theme`. This bridge syncs server → client
|
||||
// on session establishment only. Including `theme` made the effect re-fire
|
||||
// on every user selection and revert to the stale cached
|
||||
// `session.user.theme` (the PUT to /api/me/theme does not refresh the
|
||||
// better-auth session, so user.theme remains the pre-change value and
|
||||
// "wins" against the new client value).
|
||||
hasSynced.current = true;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [session?.user?.id, setTheme]);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue