🍒

[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