Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,14 +622,29 @@ export function useLocalStorage(key, initialValue) {
getLocalStorageServerSnapshot
);

// Tracks whether the item was explicitly removed via setState(null/undefined).
// This lets us distinguish "never initialized" from "intentionally cleared",
// which the storage API itself has no concept of.
const wasRemovedRef = React.useRef(false);

// Reset the removal flag when the key changes — switching keys means
// we're managing a different value entirely, so start fresh.
const prevKeyRef = React.useRef(key);
if (prevKeyRef.current !== key) {
prevKeyRef.current = key;
wasRemovedRef.current = false;
}

const setState = React.useCallback(
(v) => {
try {
const nextState = typeof v === "function" ? v(JSON.parse(store)) : v;

if (nextState === undefined || nextState === null) {
wasRemovedRef.current = true;
removeLocalStorageItem(key);
} else {
wasRemovedRef.current = false;
setLocalStorageItem(key, nextState);
}
} catch (e) {
Expand All @@ -640,15 +655,27 @@ export function useLocalStorage(key, initialValue) {
);

React.useEffect(() => {
// Only seed the initial value if the key is absent AND the user hasn't
// explicitly cleared it. Without this guard, calling setState(null) would
// remove the item, but this effect would immediately write initialValue back.
if (
getLocalStorageItem(key) === null &&
typeof initialValue !== "undefined"
typeof initialValue !== "undefined" &&
!wasRemovedRef.current
) {
setLocalStorageItem(key, initialValue);
}
}, [key, initialValue]);

return [store ? JSON.parse(store) : initialValue, setState];
// If the item is absent because it was explicitly removed, return null.
// If it was never set, fall back to initialValue as before.
const resolvedValue = store
? JSON.parse(store)
: wasRemovedRef.current
? null
: initialValue;

return [resolvedValue, setState];
}

export function useLockBodyScroll() {
Expand Down