From f958230a664425e812348719e02b2b1a55f44ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Mon, 20 Oct 2025 10:29:38 +0200 Subject: [PATCH] fix: improve cookie store --- apps/start/src/components/theme-provider.tsx | 18 +++------- apps/start/src/hooks/use-cookie-store.tsx | 37 +++++++++++++++++--- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/apps/start/src/components/theme-provider.tsx b/apps/start/src/components/theme-provider.tsx index 1a4b2388..792b57f8 100644 --- a/apps/start/src/components/theme-provider.tsx +++ b/apps/start/src/components/theme-provider.tsx @@ -18,18 +18,6 @@ export type AppTheme = z.infer; const themeStorageKey = 'ui-theme'; -const getStoredUserTheme = createIsomorphicFn() - .server((): UserTheme => 'system') - .client((): UserTheme => { - const stored = localStorage.getItem(themeStorageKey); - return UserThemeSchema.parse(stored); - }); - -const setStoredTheme = clientOnly((theme: UserTheme) => { - const validatedTheme = UserThemeSchema.parse(theme); - localStorage.setItem(themeStorageKey, validatedTheme); -}); - const getSystemTheme = createIsomorphicFn() .server((): AppTheme => 'light') .client((): AppTheme => { @@ -73,7 +61,10 @@ const themes = mapKeys(themeConfig).map((key) => ({ const themeScript = (() => { function themeFn() { try { - const storedTheme = localStorage.getItem('ui-theme') || 'system'; + // Read theme from cookie + const cookies = document.cookie.split('; '); + const themeCookie = cookies.find((c) => c.startsWith('ui-theme=')); + const storedTheme = themeCookie ? themeCookie.split('=')[1] : 'system'; const validTheme = ['light', 'dark', 'system'].includes(storedTheme) ? storedTheme : 'system'; @@ -131,7 +122,6 @@ export function ThemeProvider({ children }: ThemeProviderProps) { const setTheme = (newUserTheme: UserTheme) => { const validatedTheme = UserThemeSchema.parse(newUserTheme); setUserTheme(validatedTheme); - setStoredTheme(validatedTheme); handleThemeChange(validatedTheme); }; diff --git a/apps/start/src/hooks/use-cookie-store.tsx b/apps/start/src/hooks/use-cookie-store.tsx index 360bc3a8..ec775a1b 100644 --- a/apps/start/src/hooks/use-cookie-store.tsx +++ b/apps/start/src/hooks/use-cookie-store.tsx @@ -2,10 +2,11 @@ import { useRouteContext } from '@tanstack/react-router'; import { createServerFn, createServerOnlyFn } from '@tanstack/react-start'; import { getCookies, setCookie } from '@tanstack/react-start/server'; import { pick } from 'ramda'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { z } from 'zod'; const VALID_COOKIES = ['ui-theme', 'chartType', 'range'] as const; +const COOKIE_EVENT_NAME = '__cookie-change'; const setCookieFn = createServerFn({ method: 'POST' }) .inputValidator(z.object({ key: z.enum(VALID_COOKIES), value: z.string() })) @@ -25,15 +26,41 @@ export function useCookieStore( ) { const { cookies } = useRouteContext({ strict: false }); const [value, setValue] = useState((cookies?.[key] ?? defaultValue) as T); + const ref = useRef(Math.random().toString(36).substring(7)); + + useEffect(() => { + const handleCookieChange = ( + event: CustomEvent<{ key: string; value: T; from: string }>, + ) => { + if (event.detail.key === key && event.detail.from !== ref.current) { + setValue(event.detail.value); + } + }; + + window.addEventListener( + COOKIE_EVENT_NAME, + handleCookieChange as EventListener, + ); + return () => { + window.removeEventListener( + COOKIE_EVENT_NAME, + handleCookieChange as EventListener, + ); + }; + }, [key]); return useMemo( () => [ value, - (value: T) => { - console.log('setting cookie', key, value); - setValue(value); - setCookieFn({ data: { key, value: String(value) } }); + (newValue: T) => { + setValue(newValue); + setCookieFn({ data: { key, value: String(newValue) } }); + window.dispatchEvent( + new CustomEvent(COOKIE_EVENT_NAME, { + detail: { key, value: newValue, from: ref.current }, + }), + ); }, ] as const, [value, key],