Skip to content

useTheme throws during SSR when localStorageKey is configured #2974

Open

Description

@Jarvis636431

Description

useTheme({ localStorageKey: 'theme' }) throws during server-side rendering when browser localStorage is unavailable.

The hook guards window.matchMedia with isBrowser, but its theme-mode initializer still accesses localStorage.getItem whenever localStorageKey is non-empty. This remains present on current master (43003f3bd5e6921173cfa2a791ed9d34e5221935, package version 3.10.1). The earlier SSR fix #2691 protected the media query path, but not this storage path.

Minimal reproduction

Run in a server environment without browser storage:

import { renderToString } from 'react-dom/server';
import { useTheme } from 'ahooks';

function App() {
  const { theme, themeMode } = useTheme({ localStorageKey: 'theme' });
  return <span>{themeMode}:{theme}</span>;
}

renderToString(<App />);

Expected behavior

Rendering succeeds using the existing server defaults (themeMode: 'system', theme: 'light'). Browser rendering continues to read and persist the selected mode.

Actual behavior

The initializer accesses unavailable localStorage. The regression test with localStorage explicitly unavailable throws TypeError: Cannot read properties of undefined (reading 'getItem'); runtimes without that global can throw ReferenceError instead.

A server runtime that exposes its own localStorage also causes the hook to read that storage rather than using browser-only persistence.

Proposed fix and verification

Guard storage reads and writes with the existing isBrowser utility, following the repository's useLocalStorageState pattern.

Node-environment renderToString regressions fail on the original source and pass with the guard. Browser tests verify persisted modes, normal writes, empty/omitted keys, and stable setter references. A PR with the implementation and tests will follow.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions