Skip to content

Commit 839e74b

Browse files
liangweifengclaude
andcommitted
perf: fine-grained Zustand selectors in App and Sidebar
- App.tsx: split (s) => s.config into (s) => s.config.theme and (s) => s.config.uiLanguage — prevents the root component from re-rendering on every config change (history added, provider changed, dictionary updated, etc.) - Sidebar.tsx: (s) => s.config → (s) => s.config.theme — sidebar only needs theme for the dark/light toggle, no longer re-renders when unrelated config fields change Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 65c6431 commit 839e74b

2 files changed

Lines changed: 11 additions & 10 deletions

File tree

src/App.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ function applyTheme(theme: string) {
2424
export default function App() {
2525
const [page, setPage] = useState<PageID>('dashboard');
2626
const [settingsOpen, setSettingsOpen] = useState(false);
27-
const config = useConfigStore((s) => s.config);
27+
const theme = useConfigStore((s) => s.config.theme);
28+
const uiLanguage = useConfigStore((s) => s.config.uiLanguage);
2829
const loaded = useConfigStore((s) => s.loaded);
2930
const load = useConfigStore((s) => s.load);
3031
const { setLocale } = useTranslation();
@@ -46,24 +47,24 @@ export default function App() {
4647
// Sync UI language from config to i18n context
4748
useEffect(() => {
4849
if (!loaded) return;
49-
const lang = config.uiLanguage === 'auto' ? detectLocale() : config.uiLanguage as Locale;
50+
const lang = uiLanguage === 'auto' ? detectLocale() : uiLanguage as Locale;
5051
setLocale(lang);
51-
}, [loaded, config.uiLanguage, setLocale]);
52+
}, [loaded, uiLanguage, setLocale]);
5253

5354
// Apply theme on config change
5455
useEffect(() => {
5556
if (!loaded) return;
56-
applyTheme(config.theme);
57-
}, [loaded, config.theme]);
57+
applyTheme(theme);
58+
}, [loaded, theme]);
5859

5960
// Listen for system theme changes when in 'system' mode
6061
useEffect(() => {
61-
if (!loaded || config.theme !== 'system') return;
62+
if (!loaded || theme !== 'system') return;
6263
const mq = window.matchMedia('(prefers-color-scheme: dark)');
6364
const handler = () => applyTheme('system');
6465
mq.addEventListener('change', handler);
6566
return () => mq.removeEventListener('change', handler);
66-
}, [loaded, config.theme]);
67+
}, [loaded, theme]);
6768

6869
// Check if this is the overlay window
6970
if (window.location.hash === '#/overlay') {

src/components/layout/Sidebar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ const navItems: Array<{ id: PageID; i18nKey: string; icon: JSX.Element }> = [
3333
];
3434

3535
export function Sidebar({ current, onNavigate, onOpenSettings }: SidebarProps) {
36-
const config = useConfigStore((s) => s.config);
36+
const theme = useConfigStore((s) => s.config.theme);
3737
const set = useConfigStore((s) => s.set);
3838
const { t } = useTranslation();
3939

4040
const toggleTheme = () => {
41-
const next = config.theme === 'dark' ? 'light' : 'dark';
41+
const next = theme === 'dark' ? 'light' : 'dark';
4242
set('theme', next);
4343
};
44-
const isDark = config.theme === 'dark';
44+
const isDark = theme === 'dark';
4545

4646
return (
4747
<div className="w-[200px] bg-surface-50 dark:bg-surface-950 border-r border-surface-200 dark:border-surface-800/60 flex flex-col">

0 commit comments

Comments
 (0)