3秒でわかる
Reactの状態をlocalStorageと同期させる自作フック。画面の再読み込み後も、選んだ設定や入力内容を保ったままにできます。
もう少し詳しく
どういうものか
useLocalStorage は、Reactの useState と同じ使い心地でありながら、値の変化を自動で localStorage へ書き戻すカスタムフックです。標準では用意されておらず、各プロジェクトで自作するか、ライブラリのものを使います。const [theme, setTheme] = useLocalStorage("theme", "light") のように、キーと初期値を渡して使います。
なぜ必要か
useState の値はメモリ上にしかないので、再読み込みで消えます。ダークモードの設定、絞り込み条件、書きかけのフォームなどは、消えると毎回選び直しになります。かといって、コンポーネントのあちこちに localStorage.setItem を書くと、保存漏れや読み込み時のパース処理が散らばります。フックにまとめれば、使う側は状態管理と同じ書き方のまま永続化まで手に入ります。
具体例
import { useState, useEffect } from "react";
function useLocalStorage(key, initialValue) {
// 初期化関数を渡すことで、読み出しは初回だけになる
const [value, setValue] = useState(() => {
try {
const raw = window.localStorage.getItem(key);
return raw === null ? initialValue : JSON.parse(raw);
} catch {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
// 容量超過や保存が禁止された環境では黙って諦める
}
}, [key, value]);
return [value, setValue];
}
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage("theme", "light");
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
現在のテーマは {theme}
</button>
);
}つまずきやすいところ
useState(localStorage.getItem(key)) と直接書くと、再レンダリングのたびに読み出しが走ります。上のように関数を渡す遅延初期化にすると、初回だけの実行になります。
サーバーサイドレンダリングを使う環境では、初回の描画がサーバー上で行われ、そこに window がありません。Next.js でそのまま使うと window is not defined で落ちます。読み出しを useEffect の中へ移すか、typeof window === "undefined" を確認して初期値を返します。ただしこの場合、サーバーが描いた内容とブラウザでの内容が食い違い、ハイドレーションの警告が出ることがあります。保存された値の反映を1フレーム遅らせる作りにするのが現実的な対処です。
別のタブで同じキーを書き換えても、こちらのタブの状態は変わりません。同期させたいなら storage イベントを購読します。
似た用語との違い
useState はメモリ上だけ、useLocalStorage は端末に残る、useContext は複数コンポーネントで共有する仕組みです。「消えないこと」と「共有すること」は別の課題なので、両方必要ならフックで永続化した値をコンテキストに載せます。