🐢
Jotaiを使って入力フォームのデータをリロード後も保持する
これは何?
- 入力フォームのデータをブラウザバックやブラウザリロード時も保持する方法について記載します
- 環境
- Next.js 15.1.2(App Router)
- React 19.0.0
- Jotai 2.11.0
- Conform 1.2.2
ユースケース
- 入力フォームは、
入力画面 => 確認画面 => サンクスページ
とします。 - サンクスページに遷移したら入力データがクリアされます。
- サンクスページに移動しない限り、ブラウザを閉じたりリロードしたりブラウザバックしても入力データは保持されます。
実装方針
-
Jotai
のatomWithStorage
を使います。 - ストレージはローカルストレージを使います。
コード
atom定義
app/lib/atoms/atoms.tsx
import { atomWithStorage } from "jotai/utils";
export const emailAtom = atomWithStorage<string>("emailAtom", "", undefined, {
getOnInit: true,
});
export const nameAtom = atomWithStorage<string>("nameAtom", "", undefined, {
getOnInit: true,
});
入力フォーム
app/ui/use-conform/create/form.tsx
"use client";
# ...snip
import { emailAtom, nameAtom } from "@/app/lib/atoms/atoms";
import { useAtom } from "jotai/react";
export default function Form() {
const form = useFormMetadata();
const [email] = useField<string>("email");
const [name] = useField<string>("name");
const [emailState, setEmail] = useAtom(emailAtom);
const [nameState, setName] = useAtom(nameAtom);
return (
<form id={form.id} onSubmit={form.onSubmit} noValidate>
# ...snip
<Input
type="email"
key={email.key}
name={email.name}
defaultValue={emailState || email.value}
placeholder="sample@example.com"
onChange={(e) => setEmail(e.target.value)}
/>
# ...snip
<Input
type="text"
key={name.key}
name={name.name}
defaultValue={nameState || name.value}
placeholder="John Doe"
onChange={(e) => setName(e.target.value)}
/>
# ...snip
</form>
);
}
useAtom
で定義したステートデータをフォームのdefaultValue
に定義し、ステートデータが存在したら初期表示に使います。
サンクスページ
app/thanks/page.tsx
"use client";
import { emailAtom, nameAtom } from "@/app/lib/atoms/atoms";
import { useSetAtom } from "jotai/react";
import { RESET } from "jotai/utils";
export default function Page() {
useSetAtom(emailAtom)(RESET);
useSetAtom(nameAtom)(RESET);
return <p>thanks!</p>;
}
ページ遷移したら入力フォームに設定したatomをローカルストレージから削除します。
もし、汎用的なページ(たとえばデータ一覧ページ)に遷移したときに消したい場合、データ保存に成功したらサーバ側でワンタイムクッキーを定義して、遷移先のページでクッキー判定して削除ロジックを起動させると良さそうです。
以下、ワンタイムクッキーの設定例です:
app/lib/actions.ts
"use server";
import { cookies } from "next/headers";
export async function storeAction() {
await dataStore();
(await cookies()).set("successStoreData", "true", { maxAge: 0 });
revalidatePath("/dashboard/datas");
redirect("/dashboard/datas");
}
備考
- 個人的にはローカルストレージよりセッションストレージを使いたかったのですが、うまく動かなかったのでローカルストレージにしてます(似たissue: https://github.com/pmndrs/jotai/issues/2581)
- 以前はlocation-stateを使用して状態保存していました。状態管理全般に
Jotai
を使いたかったので、保存のためだけに違うライブラリを使うのもなんだか非効率に思い、location-state
は撤去しました(location-stateのほうが状態保存には軽量でシンプルなので、もしかしたら共存させるかもしれません。)
Discussion