Part 9 · state を共有する
52 / 64 章
ブラウザに保存する
リロードしても、閉じても残る状態
前の章で、URL に状態を置きました。 ですが URL に出したくないものもあります。
ダークモードの設定、閉じたお知らせ、入力途中の下書き。その人にだけ残ってほしいものです。
そういうときは、ブラウザの保存領域を使います。
やはり形は同じ
// いつもの
const [name, setName] = useState("");
// 置き場所をブラウザの保存領域にしたもの
const [name, setName] = useLocalStorageState("key-name", { defaultValue: "" });この Part で 3 つめですが、また同じ形です。 これは偶然ではありません。あとでまとめて扱います。
ブラウザに保存される入力
入力してからリロードしてみる
まだ入力されていません
入力してからページを再読み込みしてみてください
入力してからページを再読み込みしてください。入力が残っています。タブを閉じて開き直しても残ります。
local-storage.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import useLocalStorageState from "use-local-storage-state";
export function LocalStorageDemo() {
// これも形は useState と同じ。置き場所がブラウザの保存領域になっただけ
const [name, setName] = useLocalStorageState("react-lesson-demo-name", {
defaultValue: "",
});
return (
<div className="flex flex-col gap-4">
<Input
placeholder="名前を入力してみる"
aria-label="名前を入力してみる"
value={name}
onChange={(event) => setName(event.target.value)}
/>
<div className="rounded-md border p-3">
{name === "" ? (
<span className="text-muted-foreground">まだ入力されていません</span>
) : (
<span>こんにちは、{name} さん</span>
)}
</div>
<Button size="sm" variant="outline" onClick={() => setName("")}>
消す
</Button>
<p className="text-muted-foreground">
入力してからページを再読み込みしてみてください
</p>
</div>
);
}素で書くと、意外と面倒
ブラウザの保存領域は localStorage という名前で、 React がなくても使えます。ただ、React から使うと問題が出ます。
// 素朴に書くとこうなるが、これは動かない
const [name, setName] = useState(localStorage.getItem("name") ?? "");面倒なのは次の 3 点です。
- サーバー側では動かない … Next.js は画面をサーバーでも組み立てます。そこに
localStorageは存在しないのでエラーになります - 保存し忘れる … 更新のたびに書き込む処理を自分で呼ぶ必要があります
- 文字列しか入らない … オブジェクトを入れるには毎回変換が要ります
デモで使っている use-local-storage-state は、 これを全部引き受けて useState と同じ形にしてくれます。
local-storage.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import useLocalStorageState from "use-local-storage-state";
export function LocalStorageDemo() {
// これも形は useState と同じ。置き場所がブラウザの保存領域になっただけ
const [name, setName] = useLocalStorageState("react-lesson-demo-name", {
defaultValue: "",
});
return (
<div className="flex flex-col gap-4">
<Input
placeholder="名前を入力してみる"
aria-label="名前を入力してみる"
value={name}
onChange={(event) => setName(event.target.value)}
/>
<div className="rounded-md border p-3">
{name === "" ? (
<span className="text-muted-foreground">まだ入力されていません</span>
) : (
<span>こんにちは、{name} さん</span>
)}
</div>
<Button size="sm" variant="outline" onClick={() => setName("")}>
消す
</Button>
<p className="text-muted-foreground">
入力してからページを再読み込みしてみてください
</p>
</div>
);
}入れてはいけないもの
そのほか、次のような制約があります。
- 容量が小さい(おおよそ 5MB 程度)。 大量のデータは入りません
- その端末のそのブラウザにしか残らない。 スマホで見たら別の状態です
- ユーザーがいつでも消せる
local-storage.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import useLocalStorageState from "use-local-storage-state";
export function LocalStorageDemo() {
// これも形は useState と同じ。置き場所がブラウザの保存領域になっただけ
const [name, setName] = useLocalStorageState("react-lesson-demo-name", {
defaultValue: "",
});
return (
<div className="flex flex-col gap-4">
<Input
placeholder="名前を入力してみる"
aria-label="名前を入力してみる"
value={name}
onChange={(event) => setName(event.target.value)}
/>
<div className="rounded-md border p-3">
{name === "" ? (
<span className="text-muted-foreground">まだ入力されていません</span>
) : (
<span>こんにちは、{name} さん</span>
)}
</div>
<Button size="sm" variant="outline" onClick={() => setName("")}>
消す
</Button>
<p className="text-muted-foreground">
入力してからページを再読み込みしてみてください
</p>
</div>
);
}理解できたか確かめる
確認クイズ
ログイン用のトークンを localStorage に保存してよい?
確認クイズ
localStorage を useState の初期値に直接書くと何が起きる?
local-storage.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import useLocalStorageState from "use-local-storage-state";
export function LocalStorageDemo() {
// これも形は useState と同じ。置き場所がブラウザの保存領域になっただけ
const [name, setName] = useLocalStorageState("react-lesson-demo-name", {
defaultValue: "",
});
return (
<div className="flex flex-col gap-4">
<Input
placeholder="名前を入力してみる"
aria-label="名前を入力してみる"
value={name}
onChange={(event) => setName(event.target.value)}
/>
<div className="rounded-md border p-3">
{name === "" ? (
<span className="text-muted-foreground">まだ入力されていません</span>
) : (
<span>こんにちは、{name} さん</span>
)}
</div>
<Button size="sm" variant="outline" onClick={() => setName("")}>
消す
</Button>
<p className="text-muted-foreground">
入力してからページを再読み込みしてみてください
</p>
</div>
);
}この章のまとめ
- ブラウザの保存領域に置くと、リロードしても、閉じても残る
- 素で書くと、サーバー側で動かない・保存し忘れる・型変換が要る
- 秘密の情報は入れない。開発者ツールから丸見え
- 向いているのは消えても致命的でない、その人だけの設定
local-storage.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import useLocalStorageState from "use-local-storage-state";
export function LocalStorageDemo() {
// これも形は useState と同じ。置き場所がブラウザの保存領域になっただけ
const [name, setName] = useLocalStorageState("react-lesson-demo-name", {
defaultValue: "",
});
return (
<div className="flex flex-col gap-4">
<Input
placeholder="名前を入力してみる"
aria-label="名前を入力してみる"
value={name}
onChange={(event) => setName(event.target.value)}
/>
<div className="rounded-md border p-3">
{name === "" ? (
<span className="text-muted-foreground">まだ入力されていません</span>
) : (
<span>こんにちは、{name} さん</span>
)}
</div>
<Button size="sm" variant="outline" onClick={() => setName("")}>
消す
</Button>
<p className="text-muted-foreground">
入力してからページを再読み込みしてみてください
</p>
</div>
);
}