今回のゴール

  • チェックボックス表示
  • 押下するとDBのcompleted(完了済)がtrueへ更新される

CRUDとは

  • 登録機能(Create)
  • 参照機能(Read)
  • 変更機能(Update)
  • 削除機能(Delete)

データ取得と表示の切り替え

findManyを使用して条件に合うレコードを複数件取得する

import { prisma } from '@/lib/prisma';
import { addTodo, toggleTodo } from './actions';

export default async function Page() {
  const todos = await prisma.todo.findMany({
    where: { userId: 1 },
    orderBy: { createdAt: 'desc' },
  });

  return (
    <div>
      <h1 className='text-2xl font-black'>Todo</h1>

      <form action={addTodo}>
        <input
          type='text'
          name='title'
          placeholder='やることを書く'
          className='border h-10'
        />
        <button
          type='submit'
          className='ml-2 px-4 py-2 bg-blue-500 text-white rounded'
        >
          追加
        </button>
      </form>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id} className='flex items-center gap-2'>
            <form action={toggleTodo}>
              <input type='hidden' name='id' value={todo.id} />
              <input
                type='hidden'
                name='completed'
                value={String(todo.completed)}
              />

              <button type='submit' className='flex items-center gap-2'>
                <input
                  key={String(todo.completed)}
                  type='checkbox'
                  checked={todo.completed}
                  readOnly
                  tabIndex={-1}
                  aria-hidden
                  className='pointer-events-none'
                />

                <span
                  className={todo.completed ? 'line-through text-gray-400' : ''}
                >
                  {todo.title}
                </span>
              </button>
            </form>
          </li>
        ))}
      </ul>
    </div>
  );
}

actions.tsxにはtoggleTodo関数を追加し、最後にはrevalidateさせ新しい表示を即座に反映させるようにする。(revalidatePathを設定しない場合、リロードしないと最新表示にならない)

'use server';

import { prisma } from '@/lib/prisma';
import { revalidatePath } from 'next/cache';

export async function addTodo(formData: FormData) {
  const title = formData.get('title') as string;

  if (!title) return;

  await prisma.todo.create({
    data: {
      title,
      userId: 1, // 仮ログイン
    },
  });
}

export async function toggleTodo(formData: FormData) {
  const id = Number(formData.get('id'));
  const completed = formData.get('completed') === 'true';

  await prisma.todo.update({
    where: { id },
    data: {
      completed: !completed,
    },
  });
  revalidatePath('/todos');
}

TIPS

revalidateしているおかげで、todo.completedが最新のデータが取得できているはずなのだが、inputタグのcheckboxがcheckedに切り替わるタイミングとspanタグの斜線がつくタイミングがずれてしまう。これがどうやら、inputタグはブラウザが状態を持つもので再生成しないと動きがズレることがある、が原因のよう。(Server Componentの再レンダリングでもDOMが使い回されるケースがある)
そのため、inputタグ(checkbox)にkeyを持たせて変化を検知させ、ReactのDOM再利用をさせないことで解決。

revalidatePath って

そのpathに対するSever Component のキャッシュを無効化して次のリクエストで再実行するAPI

  • Server Component はキャッシュされる
    ビルド時 or 初回リクエスト時に実行され、結果はキャッシュされる。そのため、Sever Action(actions.ts)でDBを更新しても、ページ(page.tsxのfindManyとかでデータを取得する際)のキャッシュはそのまま。
  • Actionが終わるとNextはページを再取得しようとするが、キャッシュが有効な場合は古い結果が返る
    Server Action実行後にキャッシュを無効化しないと古いデータが表示され続ける

今回できたこと

  • チェックボックス表示させ、押下するとDBのcompletedの切り替えができる
  • 完了済の場合はTODOに斜線を引かせる

c.sakyou

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA