[Next.js]Server Actionで取得した値をどうやって遷移に持ち越そうか考えた時の話
Next.jsを使って、アプリを作っていました。
フロントエンドもバックエンドもNext.js(×ts)のみで作りたかったので、基本的なサーバーとのやり取りはNextのServer Actionを使って行っています。
また、動的なコンポーネント以外はできるだけサーバーコンポーネントで管理しています。
(記事作成にあたり、不要なレイアウトや定義は省略しています。
やろうとしたこと
formを作成し、ボタン押下時にサーバーアクションが発火します。
サーバーの処理が完了したら、次のページに遷移します。
この時、この処理で取得した結果を次のページに表示させるようにしたいと思いました。
##form.tsx
"use client";
import { Button } from "@/components/commons/button";
import { FC, useActionState } from "react";
import { create, State } from "@/api/stories";
type Props = {
content: string;
};
const initialState: State = {content: null };
export const Form: FC<Props> = ({ content }) => {
const [state, formAction, isPending] = useActionState(create, initialState);
return (
<form action={formAction}>
<textarea
id="content"
name="content"
value={content}
onChange={handleChange}
/>
<button type="submit">
完成
</button>
</form>
);
};
##server-action.ts
"use server"
import { paths } from '@/utils/const';
import { redirect } from 'next/navigation';
export type State = {
content: string | null;
error?: string;
}
export const create = async (prevState: State, formData: FormData) => {
try {
await ...処理
const result = 処理の結果
return {content: result};
} catch (error) {
console.error('Error:', error);
return {
content: null,
error: 'エラーが発生しました。'
}
} finally {
redirect(paths.next);
}
};
最初に考えたこと
最初はreduxを使って、値をグローバルで管理できるようにしようと思っていました。
しかし、reduxはあくまでクライアントで動く状態管理ツールのため、サーバーアクションで呼び出すことはできません。
(そもそもこの結果は遷移先のページでしか使わないので、グローバルに管理する意味はほとんどないのですが...)
いくつかの方法
クエリパラメータに渡す
まずは、受け取った結果をクエリパラメータで渡して表示させる方法です。
## form.tsx
'use client';
...
const Form = () => {
const router = useRouter();
const [state, formAction] = useActionState(
async (prevState, formData) => {
const result = await create(prevState, formData);
// クエリパラメータとして遷移
router.push(`/result?message=${encodeURIComponent(result.content || '')}`);
return result;
},
initialState
);
...
個人的には、今回のケースでこれは微妙だと感じています。
ユーザーがクエリを自由に操れる上、後の実装で結果がなければformにリダイレクトするように設定したかったからです。
あとはURLが散らかるのもあまり好きではないです。
Cookieを使う
受け取った結果をcookieに保存する方法です。
##server-action.ts
"use server"
...
export const create = async (prevState: State, formData: FormData) => {
try {
await ...処理
const result = 処理の結果
cookies().set('result', result.message);
return {content: result};
} catch (error) {
...
シンプルで良いと思いました。
また、今回は結果が少し長くなる想定だったのと、認証にもcookieを使おうと思っていたので、値の管理は楽にできる気がします。
reduxを使う
サーバーアクション内でreduxを呼び出すことはできませんが、サーバーアクションで取得した値をクライアント側でグローバルにセットすることは可能です。
##form.tsx
'use client'
...
export const Form = () => {
const dispatch = useDispatch();
const router = useRouter();
const [state, formAction] = useActionState(
async (prev, formData) => {
const result = await create(prev, formData);
if (result.content) {
// ✅ Redux にセット!
dispatch(setResult(result.content));
router.push("/result");
}
return result;
},
initialState
);
...
ただし、遷移した先がサーバーコンポーネントだとreduxは使用できないので、ユースケースは限られてきます。
今回の場合、遷移先はサーバーコンポーネントとして扱いたかったので、Cookieを使用することにしました。
まとめ
今回は、サーバーアクションで取得した値を別のページに渡す方法について考えました。
何か認識に間違いや、他にも方法があればぜひ教えていただきたいです。
Discussion