🐢
Next.jsでフラッシュメッセージを実装する(App Router)
これは何
-
Next.js
でApp Router
を使ってフラッシュメッセージを実装する方法の紹介です - 環境
- Next 15.1.2
- React 19.0.0
ユースケース
以下のリストテーブルの右側のごみ箱アイコンをクリックしたら行を削除し、画面の右下にフラッシュメッセージ(トーストメッセージ)を表示します
動画
実装方針
- ごみ箱アイコンがクリックされたらアイコンも含めて行が消えるので、アイコンのコンポーネントに表示ロジックは持たせないようにしました
- 状態のバケツリレーを避けたかったので、Nextのcookiesを使ってページ層で一括管理するようにしました
- UIコンポーネントはshadcn/uiのSonnerを使いましたが、ネットで調べた記事によく出てきたから使ったのであって特にこだわりはありません
メッセージ表示コンポーネント
"use client";
import { ReactNode, useEffect } from "react";
import { toast } from "sonner";
export function Notify({
children,
isSuccessDeleteInvoice,
}: {
children: ReactNode;
isSuccessDeleteInvoice: boolean;
}) {
useEffect(() => {
if (isSuccessDeleteInvoice) {
toast.success("Delete invoice successfully.");
}
});
return <>{children}</>;
}
表示コンポーネントは状態を持つのでクライアントコンポーネントとして切り出し、サーバーコンポーネントから呼び出すようにしました。メインコンテンツをラップするようなコンポーネントです。
メッセージコンポーネントの埋め込み
import Table from "@/app/ui/invoices/table";
import { Notify } from "@/app/ui/invoices/notify";
import { cookies } from "next/headers";
export default async function Page() {
const isSuccessDeleteInvoice = (await cookies()).has("successDeleteInvoice");
return (
<Notify isSuccessDeleteInvoice={isSuccessDeleteInvoice}>
<Table />
</Notify>
);
}
ページ層に、メインで表示したいコンポーネントである<Table />
の親としてメッセージコンポーネントを設定します。メッセージ表示を実際に行うUIコンポーネントはapp/layout.tsx
に埋め込んでおり、Notify
コンポーネントのtoast()
が実行されると表示される仕組みです。
データ削除ロジック
"use server";
import { PrismaClient } from "@prisma/client";
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers";
const prisma = new PrismaClient();
export async function deleteInvoice(id: string, _prevState: unknown) {
await prisma.invoices.delete({
where: { id: id },
});
(await cookies()).set("successDeleteInvoice", "true", { maxAge: 0 });
revalidatePath("/dashboard/invoices");
}
FormData
を使わず、明示的にパラメータを渡す形にしたのでuseActionState
用の_prevState
はパラメータの次に書いてます(今回は状態管理しないのでunknown
型にしてます)。revalidatePath
でページの再検証をする前にmaxAge: 0
のクッキーをセットし、ページ層で一度だけ検証したら即時消失するようにします。
データ削除ボタンコンポーネント
"use client";
import { TrashIcon } from "@heroicons/react/24/outline";
import { deleteInvoice } from "@/app/lib/actions";
import { useActionState } from "react";
export function DeleteInvoice({ id }: { id: string }) {
const deleteInvoiceWithId = deleteInvoice.bind(null, id);
const [_state, formAction, pending] = useActionState(
deleteInvoiceWithId,
undefined
);
return (
<form action={formAction}>
<button
className="rounded-md border p-2 hover:bg-gray-100"
disabled={pending}
>
<span className="sr-only">Delete</span>
<TrashIcon className="w-5" />
</button>
</form>
);
}
フォームで状態表示をしないので_state
としましたが、もしかしたらuseActionState
はオーバーエンジニアリングだったかも(型エラーが出るのが怖かったのでサーバーアクション使うからuseActionState
を使うと作法的に思考しました)
以上です
感想
Rails
のようにコントローラでフラッシュメッセージを設定できる作りは便利だと思いましたのでやってみました。Next.js
でどうやるのがベストプラクティスなのかは分かってませんが、おそらくもっと良い方法があると思います。
Discussion