learning_ai_common_plat/packages/use-theme/src/use-theme.ts
saravanakumardb1 bb86bf220e fix(use-theme): remove redundant applyTheme from toggleTheme
toggleTheme was calling applyTheme() inside the state updater AND
the useEffect was also applying on state change — double DOM write.
Now toggleTheme relies solely on the useEffect, matching setTheme
behavior.
2026-03-29 12:52:19 -07:00

89 lines
2.4 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
export type Theme = 'light' | 'dark';
export interface UseThemeOptions {
/** localStorage key — default: 'theme' */
storageKey?: string;
/** Apply theme via class or data-theme attribute — default: 'class' */
attribute?: 'class' | 'data-theme';
}
export interface UseThemeReturn {
theme: Theme;
setTheme: (t: Theme) => void;
toggleTheme: () => void;
}
function readStoredTheme(key: string): Theme {
if (typeof window === 'undefined') {
return 'dark';
}
const stored = window.localStorage.getItem(key);
return stored === 'light' || stored === 'dark' ? stored : 'dark';
}
function applyTheme(theme: Theme, attribute: 'class' | 'data-theme') {
if (typeof document === 'undefined') {
return;
}
const root = document.documentElement;
if (attribute === 'data-theme') {
root.setAttribute('data-theme', theme);
} else {
root.classList.remove('light', 'dark');
root.classList.add(theme);
}
}
export function useTheme(options?: UseThemeOptions): UseThemeReturn {
const storageKey = options?.storageKey ?? 'theme';
const attribute = options?.attribute ?? 'class';
const [theme, setThemeState] = useState<Theme>(() => readStoredTheme(storageKey));
// Apply theme to DOM whenever it changes
useEffect(() => {
applyTheme(theme, attribute);
}, [theme, attribute]);
// Sync across tabs via storage event
useEffect(() => {
if (typeof window === 'undefined') return;
function handleStorage(event: StorageEvent) {
if (event.key !== storageKey) {
return;
}
if (event.newValue === 'light' || event.newValue === 'dark') {
setThemeState(event.newValue);
}
}
window.addEventListener('storage', handleStorage);
return () => window.removeEventListener('storage', handleStorage);
}, [storageKey]);
const setTheme = useCallback(
(t: Theme) => {
setThemeState(t);
if (typeof window !== 'undefined') {
window.localStorage.setItem(storageKey, t);
}
},
[storageKey]
);
const toggleTheme = useCallback(() => {
setThemeState(prev => {
const next: Theme = prev === 'dark' ? 'light' : 'dark';
if (typeof window !== 'undefined') {
window.localStorage.setItem(storageKey, next);
}
return next;
});
}, [storageKey]);
return { theme, setTheme, toggleTheme };
}