📑

Next.js Server Actionsを使ったデータ取得について

2023/05/21に公開

Next.jsのApp Routerを使って開発を進める中で、APIからデータを取得するところでつまったため、整理も兼ねてまとめます。
公式ドキュメントを見ながら試行錯誤したものとなるため誤りがあればご指摘いただければと思います。

やりたいこと

  • ユーザー選択の情報をもとにAPIからデータを再取得する
  • サーバー側で完結させる

パターン1

formActionrevalidateTagcookiesを使う。
formActionを使ってフォームの入力値を取得してcookies().set()でcookiesに値をセット、その後revalidateTag('posts')を実行してキャッシュデータを更新できるようにする。
APIのデータ取得の前にcookiesの値の有無に応じてクエリパラメータの値を設定することで、ユーザーが日付を選択してSubmitを実行するたびにデータを取得できる。

実装

page.tsx
import { cookies } from 'next/headers';
import { revalidateTag } from 'next/cache';

async function handleSubmit(formData: FormData) {
  'use server';
  // フォームデータからdateを取得
  const date = formData.get('date')
  cookies().set('date', date);

  revalidateTag('posts');
}

export default function Page() {
  const cookieStore = cookies();
  const cookieDate = cookieStore.get('date');
  // cookieDateがあればその値を使う
  const param = cookieDate != null ? cookieDate.value : "";

  const res = await fetch(`https://...?date=${param}`, { next: { tags: ['posts'] } });
  const data = await res.json();
 
  return (
    <>
      <form action={handleSubmit}>
        <input type="text" name="date" />
        <button type="submit">Submit</button>
      </form>
      {/* APIから取得したデータをコンポーネントに渡す */}
      <Component data={data} />
    </>
  );
}

パターン2

experimental_useFormStatusを使うことで、フォームの状態を取得できるみたいです。
Next.jsドキュメントの記載はこちら

const {pending, data, action, method} = useFormStatus();

ただ、以下Issueによるとサーバーコンポーネントではエラーが出るとのこと。
https://github.com/vercel/next.js/issues/49232#issuecomment-1542757637

react/react-dom/nextのバージョンを変更することで使えるみたいですが、エラーが出ていて動作確認までできていないです。

{
    "react": "experimental",
    "react-dom": "experimental",
    "next": "canary"
}

パターン3

formのonSubmitでAPIからデータを取得してuseStateに格納する。
クライアントコンポーネントでfetchを行っていますが、ログを確認した限りは問題なさそうでした。
ただ、import 'server-only'を入れるとエラーが出たため、この方法はなしかなと思います。

実装

fetch.ts
'use server';
export async function fetchData(param){
  const res = await fetch(`https://...?date=${param}`);
  const data = await res.json();

  return data;
}
component.tsx
'use client'
export const Component: FC<Props> = ({ data }) => {
  // useStateの初期値は親コンポーネントでAPIから取得したものを設定
  const [currentData, setCurrentData] = useState(data);
  const [value, setValue] = useState('');
  
  const handleChange = (event) => {
    setValue(event.target.value);
  };
  
  return (
    <>
      <form
        className={form()}
        // onSubmitの中でfetchDataを呼んでその結果をsetCurrentDataに格納
        onSubmit={async (e) => {
          e.preventDefault();
          const res = await fetchData(value);
          setCurrentData(res);
        }}
      >
        <input value={value} onChange={handleChange} />
        <button type='submit'>submit</button>
      </form>
      <FogeComponent data={currentData} />
    </>
  );
};

まとめ

ひとまずパターン1の方法で進めていますが、useFormStatusがサーバーコンポーネントでも使えるのであればパターン2の方が良い気がします。

Discussion