fix(useLocalStorage): stabilize initialValue handling to prevent unnecessary re-renders

This commit is contained in:
ahmadshaheer 2025-06-02 08:48:32 +04:30
parent ba40f9afee
commit 2b2460dbdb

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
/** /**
* A React hook for interacting with localStorage. * A React hook for interacting with localStorage.
@ -17,11 +17,23 @@ export function useLocalStorage<T>(
key: string, key: string,
initialValue: T | (() => T), initialValue: T | (() => T),
): [T, (value: T | ((prevState: T) => T)) => void, () => void] { ): [T, (value: T | ((prevState: T) => T)) => void, () => void] {
// Stabilize the initialValue to prevent unnecessary re-renders
const initialValueRef = useRef<T | (() => T)>(initialValue);
// Update the ref if initialValue changes (for cases where it's intentionally dynamic)
useEffect(() => {
if (initialValueRef.current !== initialValue) {
initialValueRef.current = initialValue;
}
}, [initialValue]);
// This function resolves the initialValue if it's a function, // This function resolves the initialValue if it's a function,
// and handles potential errors during localStorage access or JSON parsing. // and handles potential errors during localStorage access or JSON parsing.
const readValueFromStorage = useCallback((): T => { const readValueFromStorage = useCallback((): T => {
const resolvedInitialValue = const resolvedInitialValue =
initialValue instanceof Function ? initialValue() : initialValue; initialValueRef.current instanceof Function
? (initialValueRef.current as () => T)()
: initialValueRef.current;
try { try {
const item = window.localStorage.getItem(key); const item = window.localStorage.getItem(key);
@ -34,7 +46,7 @@ export function useLocalStorage<T>(
console.warn(`Error reading localStorage key "${key}":`, error); console.warn(`Error reading localStorage key "${key}":`, error);
} }
return resolvedInitialValue; return resolvedInitialValue;
}, [key, initialValue]); }, [key]);
// Initialize state by reading from localStorage. // Initialize state by reading from localStorage.
const [storedValue, setStoredValue] = useState<T>(readValueFromStorage); const [storedValue, setStoredValue] = useState<T>(readValueFromStorage);
@ -65,12 +77,14 @@ export function useLocalStorage<T>(
window.localStorage.removeItem(key); window.localStorage.removeItem(key);
// Reset state to the (potentially resolved) initialValue. // Reset state to the (potentially resolved) initialValue.
setStoredValue( setStoredValue(
initialValue instanceof Function ? initialValue() : initialValue, initialValueRef.current instanceof Function
? (initialValueRef.current as () => T)()
: initialValueRef.current,
); );
} catch (error) { } catch (error) {
console.warn(`Error removing localStorage key "${key}":`, error); console.warn(`Error removing localStorage key "${key}":`, error);
} }
}, [key, initialValue]); }, [key]);
// useEffect to update the storedValue if the key changes, // useEffect to update the storedValue if the key changes,
// or if the initialValue prop changes causing readValueFromStorage to change. // or if the initialValue prop changes causing readValueFromStorage to change.