🌀

【Next.js 13】登録、更新、削除後の一覧をリフレッシュする

2022/11/26に公開1

タイミーでフロントエンドエンジニアをしているnisshi-と申します。

Next.js 13においてデータの登録、更新、削除を行った後に一覧に遷移する場合、基本的には一覧の表示は更新後の最新の結果が表示されて欲しいケースが多いと思います。

それらをどういった実装で実現するか、一例を簡単にまとめました。

実装例

更新を行うフォームコンポーネントの一例です。
ユーザーの入力を対話的に受け止める必要がある為、Client Componentとします。


TodoUpdateForm.tsx
'use client';

import { Todo } from '@/types/todo';
import { useRouter } from 'next/navigation';
import { InputTodo, TodoForm } from './TodoForm';

type Props = {
  todo: Todo;
};

const update = (todo: InputTodo) => {
  return fetch(`/api/todo/update`, {
    method: 'POST',
    body: JSON.stringify(todo),
  });
};

export default function TodoUpdateForm({ todo }: Props) {
  const router = useRouter();

  const handleSubmit = async (data: InputTodo) => {
    await update({ ...data});
    router.push('/list');
  };

  return (
    <div>
      <TodoForm
	todo={todo}
        onSubmit={handleSubmit}
      />
    </div>
  );
}
TodoForm.tsx(例)
TodoForm.tsx
'use client';

import { Todo } from '@/types/todo';
import { FieldValues, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';

const schema = z.object({
  title: z.string().min(1, { message: 'Required.' }),
  description: z.string().optional(),
});

type Props = {
  todo? : Todo;
  onSubmit: (todo: InputTodo) => void;
};

export type InputTodo = {
  title: string;
  description: string;
};

export const TodoForm = (props: Props) => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<InputTodo>({
    defaultValues: {
      title: props.todo?.title,
      description: props.todo?.description,
    },
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FieldValues) =>
    props.onSubmit({
      title: data.title,
      description: data.description,
    });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        title : <input type="text" {...register('title')} />
        {errors.title?.message && <span>{errors.title.message}</span>}
      </div>
      <div>
        description : <input type="text" {...register('description')} />
      </div>
      <div>
        <button type="submit">submit</button>
      </div>
    </form>
  );
};

submit後にrouter.pushを利用して一覧(/list)に遷移します。

一覧画面

しかし遷移後の一覧画面ではAタスクに@@@の文字が追記されていません。

next/navigationのrouterのpushreplaceは、コンポーネントの状態を保持したまま画面遷移を行い、データの再読込みが行われない事が原因です。

https://beta.nextjs.org/docs/api-reference/use-router#userouter

解決

router.refreshを利用します。

TodoUpdateForm.tsx
'use client';

/* ~中略~ */

export default function TodoUpdateForm({ todo }: Props) {

  const handleSubmit = async (data: InputTodo) => {
    await update({ ...data});
    router.push('/list');
+   router.refresh();
  };

/* ~中略~ */
}

動かしてみましょう。

一覧が更新されています。

refreshの仕様は下記の通りです(公式docから引用)

Refresh the current route and fetch new data from the server.
This does not reset state like scroll position or forms, for example.

pushreplaceと違い、サーバーからデータをfetchし直す仕様になっています。
ただしstateやスクロール位置は保持してくれる様です。
単にwindow.location.reload()呼び出した際とは動作が明確に異なります。

余談

他に、こちらはリフレッシュではなくキャッシュのパージに当たりますが、On-demandにキャッシュをパージするrevalidate機能が存在します。

https://beta.nextjs.org/docs/data-fetching/revalidating

こちらはブログ等、ISRのキャッシュを動的にパージする目的で使われる事が多いかと思います。
開発環境(next dev)では動作せず、トークンの設定も必要です。詳しくは上記ページをご覧ください。

後書き

Next.js13においてrouterは、単にページルーティングに利用するだけではなく、CRUDやデータのリフェッチ等重要な役割を担っている中心的な機能と言えそうです。

今回ご紹介したケース以外にも適切な実装があれば、コメントでご指摘等頂けますと幸いでした。

最後に

タイミーではフロントエンド始め、エンジニアを積極的募集中です。

もしご興味を持って頂けましたら、是非一度下記をご確認頂けますと幸いでした。
https://timee.notion.site/timee/Timee-Product-Org-Entrance-Book-b7380eb4f6954e29b2664fe6f5e775f9

それではまた別の記事で。

Discussion

gontagonta

ありがとうございます!
こちらに詰まっていまして助かりました🙇