t3-stack入門 (2) T3AppにTodoを追加する

作成したサンプルアプリにTodo表示、Todo削除機能を追加していきます。

## Prismaにテーブルを追加
まず、prisma/schema.prismaにTodoモデルを追加します。
```diff
@@ -64,3 +58,11 @@ model VerificationToken {
 
   @@unique([identifier, token])
 }
+
+model Todo {
+  id            Int    @id @default(autoincrement())
+  title         String
+  description   String
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+}

```
prismaでmigration,generateを実行しておきます。
また、sqliteで表示テスト用のデータを追加しておきます。
```shell-session
~/t3-todo-example$ sqlite3 prisma/db.sqlite 
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> INSERT INTO Todo (title,description,updatedAt) VALUES ('Todoを追加する','TodoModelを追加',datetime('now'));
sqlite> INSERT INTO Todo (title,description,updatedAt) VALUES ('Mantineを追加する','Mantine UIでフォームを作成',datetime('now'));
sqlite> INSERT INTO Todo (title,description,updatedAt) VALUES ('Id/Passwordログインを実装する','パスワードログインを実装',datetime('now'));
```

## todo操作用のRouterを追加
src/server/trpc/router/example.ts
をコピーして
src/server/trpc/router/todo.ts
を作成します。
とりあえず確認のため、todo全取得用のquery,todo削除用のmutationを作成します。
```typescript
import { z } from "zod";

import { router, publicProcedure } from "../trpc";

export const todoRouter = router({
  delete: publicProcedure
    .input(z.object({ id: z.number() }))
    .mutation(({ input, ctx }) => {
      return ctx.prisma.todo.delete({
        where: { id: input.id },
      });
    }),
  getAll: publicProcedure.query(({ ctx }) => {
    return ctx.prisma.todo.findMany();
  }),
});
```

src/server/trpc/router/_app.ts
を変更して、todo用ルータを公開します。
```diff_typescript
export const appRouter = router({
  example: exampleRouter,
  auth: authRouter,
+  todo: todoRouter,
});
```

## todo操作用のページを追加
src/pages/todos.tsxを追加。
Todo一覧表示と、削除ボタンを作成します。

```typescript
import { type NextPage } from "next";
import Head from "next/head";

import { trpc } from "../utils/trpc";

const Todo: NextPage = () => {
  const todos = trpc.todo.getAll.useQuery();
  const deleteTodo = trpc.todo.delete.useMutation();
  return (
    <>
      <Head>
        <title>Todo</title>
        <meta name="description" content="Generated by create-t3-app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className="flex min-h-screen flex-col items-center justify-center">
        <h1>Todo</h1>
        <div>
          {todos.data?.map((todo) => (
            <div key={todo.id} className="pt-10">
              <p>{todo.title}</p>
              <button
                onClick={() => {
                  deleteTodo.mutate({ id: todo.id });
                }}
                className="bg-cyan-300"
              >
                Delete
              </button>
            </div>
          ))}
        </div>
      </main>
    </>
  );
};

export default Todo;
```

一応、表示テスト用のTodoが表示され、Deleteボタンで削除処理が動いていますが、Deleteボタンを押した後、画面をリロードしないと削除したTodoが消えてくれませんでした。

https://www.nexxel.dev/blog/ct3a-guestbook

上記のWebサイトを見るとt3-appではtanstack-queryが使われていて、Mutationを起こした後、Queryのキャッシュをinvalidateする必要があるようです。
```diff_typescript
+  const utils = trpc.useContext();
-  const deleteTodo = trpc.todo.delete.useMutation()
+  const deleteTodo = trpc.todo.delete.useMutation({
+    onSettled: () => {
+      utils.todo.getAll.invalidate();
+    },
+  });
```
リンク先のBlogではもっと複雑な処理をしているみたいですが、これはDeleteのAPI呼び出しの段階でどのTodoが削除されるかはわかっているので、getAllのqueryのキャッシュを更新して画面の再作画をするみたいな処理をしているようです。そうすると、ほかのクライアントからの更新がなければ画面の更新を高速にすることができます(optimisticUpdate)。
ただ、そこまでして画面の描画を最適化する必要はあまりないのではないでしょうか。
今度はちゃんとDeleteボタンを押したら一覧から削除されています。

全体の変更は以下の通りです。

https://github.com/bioflowy/t3-todo-example/commit/e878b3cd2b328ba5b19e806023a80983deedfc34

以上、かなり簡単にAPI追加と、クライアント画面の作成ができることがわかりました。
次はMantineを導入して画面を整理したいとおもいます。