تكرار السؤال: …
الإجابة المختصرة
نبني Hook يجمع بين useState للقيمة المحلية و useEffect للاستماع لأحداث window.addEventListener("storage") لقراءة التحديثات الصادرة من النوافذ والتبويبات الأخرى.
الشرح
يجب على الـ Hook قراءة القيمة الأولية بأمان من localStorage مع معالجة أخطاء JSON parsing و SSR (عدم توفر window على الخادم). لتسجيل التغييرات بين مختلف التبويبات في المتصفح، يتم الاستماع لحدث "storage" وتحديث الحالة المحلية تلقائياً.
مثال بالكود
import { useCallback, useEffect, useState } from "react";
function useLocalStorage<T>(key: string, initialValue: T): [T, (val: T | ((prev: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === "undefined") return initialValue; // SSR: no window on the server
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
} catch {
return initialValue; // corrupted JSON or storage disabled
}
});
// Persist every change; the functional updater form of setState always sees the latest value
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(storedValue));
}, [key, storedValue]);
// Sync with other tabs/windows: the "storage" event fires only in the OTHER documents
useEffect(() => {
const onStorage = (event: StorageEvent) => {
if (event.storageArea === window.localStorage && event.key === key && event.newValue !== null) {
setStoredValue(JSON.parse(event.newValue) as T);
}
};
window.addEventListener("storage", onStorage);
return () => window.removeEventListener("storage", onStorage);
}, [key]);
const setValue = useCallback((val: T | ((prev: T) => T)) => setStoredValue(val), []);
return [storedValue, setValue];
}أخطاء شائعة
- قراءة localStorage مباشرة في جسم المكون أثناء SSR مما يؤدي لانهيار السيرفر أو خطأ Hydration mismatch.
أسئلة متابعة
- كيف تصمم Custom Hook للـ Debouncing للقيم أو الدوال مع إلغاء العمليات السابقة؟
- كيف تبني Custom Hook للـ Infinite Scroll باستخدام IntersectionObserver؟