Hook useLocalStorage (React)
Pengganti terus untuk useState yang mengingati nilainya selepas reload dan kekal selari antara tab.
import { useEffect, useRef, useState } from "react";
/**
* Like useState, but the value is saved in localStorage and
* restored on the next visit. Values must be JSON-serialisable.
*/
export function useLocalStorage(key, initialValue) {
const initialRef = useRef(initialValue);
const [value, setValue] = useState(() => {
try {
const stored = window.localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
} catch {
// No window (server render), storage blocked, or invalid JSON
return initialValue;
}
});
// Save whenever the value changes
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
// Storage full or disabled — keep working in memory
}
}, [key, value]);
// Stay in sync when another tab changes the same key
useEffect(() => {
function onStorage(event) {
if (event.storageArea !== window.localStorage || event.key !== key) return;
try {
setValue(event.newValue !== null ? JSON.parse(event.newValue) : initialRef.current);
} catch {
// Ignore values we can't parse
}
}
window.addEventListener("storage", onStorage);
return () => window.removeEventListener("storage", onStorage);
}, [key]);
return [value, setValue];
}Custom hook hanyalah fungsi yang namanya bermula dengan use dan memanggil hook lain. Hook ini membalut useState dan menambah dua side effect: simpan ke localStorage bila nilai berubah, dan dengar perubahan daripada tab lain.
Baris demi baris
useState(() => …)guna lazy initialiser: fungsi itu hanya berjalan pada render pertama, jadi kita baca dan parse storan sekali sahaja, bukan pada setiap render.localStoragehanya simpan string, jadi nilai melaluiJSON.stringifysemasa disimpan danJSON.parsesemasa dibaca. Sebab itu nilai mesti mesra JSON (tiada fungsi, Date akan jadi string).- Setiap panggilan storan dalam
try/catch: storan boleh dimatikan, penuh (QuotaExceededError) atau mengandungi JSON tidak sah. Hook ini kembali kepada state biasa dalam memori dan tidak merosakkan component anda. useEffectpertama berjalan selepas render setiap kalikeyatauvalueberubah, jadi proses simpan tak melambatkan render itu sendiri.- Pelayar mencetuskan event
storagedalam tab lain bagi laman yang sama bila satu tab menulis. Effect kedua mendengarnya dan mengemas kini state, kemudian membuang listener dalam fungsi cleanup. initialRefmengingatiinitialValueyang pertama, jadi menghantar array/objek literal baharu pada setiap render tidak menyebabkan listener dilanggan semula.
Ketahui hadnya
Jangan simpan rahsia atau token di sini — mana-mana skrip pada halaman boleh membaca localStorage. Dengan server rendering (contohnya Next.js), server sentiasa render initialValue; guna hook ini dalam client component sahaja. Menukar key semasa berjalan tidak memuat semula nilai tersimpan — remount component itu (beri prop key yang berbeza).
Cuba: buat projek React dalam Playground, tampal hook dan contoh penggunaan, kemudian tekan Run dua kali — nilai kekal kerana preview meniru localStorage bagi setiap projek.
Cara guna
import { useLocalStorage } from "./useLocalStorage.js";
export default function App() {
const [name, setName] = useLocalStorage("profile-name", "");
const [fontSize, setFontSize] = useLocalStorage("reader-font-size", 16);
return (
<main style={{ fontSize }}>
<label>
Your name{" "}
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
<p>Hello, {name || "stranger"}! Reload — I still remember you.</p>
<button type="button" onClick={() => setFontSize((s) => s + 2)}>A+</button>
<button type="button" onClick={() => setFontSize((s) => Math.max(12, s - 2))}>A−</button>
</main>
);
}