Skip to content

fix: handle localStorage null (approach B: sentinel value)#83

Closed
astoilkov wants to merge 1 commit intomainfrom
fix/localstorage-null-sentinel-value
Closed

fix: handle localStorage null (approach B: sentinel value)#83
astoilkov wants to merge 1 commit intomainfrom
fix/localstorage-null-sentinel-value

Conversation

@astoilkov
Copy link
Owner

@astoilkov astoilkov commented Feb 21, 2026

Summary

Fixes #80 — Firefox with dom.storage.enabled: false sets localStorage to null, causing the hook to break.

Approach: Fix initial ref sentinel value

The root cause: storageItem.current.string is initialized to null, and when localStorage.getItem() fails, goodTry returns undefined which gets coerced to null. Since both values are null, the comparison string !== storageItem.current.string is false, skipping the parsing branch — the value stays undefined instead of defaultValue.

Changes:

  1. Sentinel value: Changed storageItem ref initial string from null to undefined, so first-render comparison null !== undefined is true — the parsing branch always runs
  2. Default-value writing: Replaced goodTry with try-catch that falls back to inMemoryData.set(key, defaultValue) when localStorage is unavailable
  3. removeItem: Reset storageItem.current.string to undefined so getSnapshot re-parses correctly after removal when localStorage is unavailable

Why this approach:

  • Fixes the root cause — the null-null comparison that skips parsing
  • The undefined sentinel is semantically correct: "not yet read from localStorage"
  • The try-catch fallback in default-value writing ensures inMemoryData is populated, making the existing isPersistent logic work correctly
  • The removeItem sentinel reset handles the edge case of removing + re-defaulting when localStorage is unavailable

Alternative approach

See PR #82 for approach A (detect unavailability) — a different way to solve the same issue.

Next.js test projects

To test in Firefox with dom.storage.enabled: false in about:config:

Test plan

  • Added 7 tests simulating localStorage being null via vi.spyOn(window, 'localStorage', 'get').mockReturnValue(null)
  • Tests cover: defaultValue, isPersistent, setValue, setValue with callback, removeItem, no defaultValue

Use undefined as initial sentinel for storageItem ref instead of null, so
the first render parsing branch always runs. Replace goodTry with try-catch
in default-value writing to fall back to inMemoryData when localStorage
is unavailable.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

isPersistent remains true and the package breaks on Firefox with dom.storage.enabled set to false

1 participant