【12-2】Next.js app routerのチュートリアルやってみる(React Server Actionsを使ったデータ更新処理)

# はじめに

Next.js app routerのチュートリアルの第12-2章のアウトプットをします。

https://nextjs.org/learn/dashboard-app/mutating-data

<details><summary>前の記事</summary>

【01】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/af58da3d20cbc790e767

【02】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/edf450b3ee135e83d1e8

【03】Next.js app routerのチュートリアルやってみる
https://qiita.com/naoyuki2/items/612221eac233aa9cbb74

【04】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/62f9beccbfe36eaf7f90

【05】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/8b71b1d1df7c9435a9c9

【06】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/58130c3cfbaf8a573de2

【07】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/2c2da0f8071e60454679

【08】Next.js app routerのチュートリアルやってみる

https://qiita.com/naoyuki2/items/45f45fcb9cc14506f79f

【09】Next.js app routerのチュートリアルやってみる(loading.tsxとSuspenseでストリーミング)

https://qiita.com/naoyuki2/items/717694288ec6017a3af2

【10】Next.js app routerのチュートリアルやってみる(部分的な事前レンダリング)

https://qiita.com/naoyuki2/items/8062f755b0679fe925b1

【11-1】Next.js app routerのチュートリアルやってみる(URLパラメーターを利用した検索機能)

https://qiita.com/naoyuki2/items/2be9503ac80fc4a1fa6a

【11-2】Next.js app routerのチュートリアルやってみる(URL パラメータを利用したページネーション)

https://qiita.com/naoyuki2/items/fd00dc2b376e7d87fb44

【12-1】Next.js app routerのチュートリアルやってみる(React Server Actionsを使ったデータ作成処理)

https://qiita.com/naoyuki2/items/04ffef203ae798f8c7bc

</details>

# 第12-2章 データの変更

この章では以下を学びました。

- データの更新(Update)

# データの更新(Update)

データを更新するフォームは、データを作成するフォームと似ていますが、

データベース内のレコードを更新するために請求書の`id`を渡す必要がある点が異なります。

請求書を更新するには次の手順を実行します。

1. 動的ルートセグメントを作成する
1. `id`を取得する
1. データベースから特定のデータを取得する
1. データを更新する

# 1. 動的ルートセグメントを作成する

Next.jsでは、データに基づいてルートを作成したい場合に、動的ルートセグメントを作成できます。

フォルダー名を角括弧[]で囲むことによって、動的ルート セグメントを作成できます。

フォルダー内に`/invoices/[id]/edit/page.tsx`という新しい動的ルート作成しましょう。

ファイル構造は次のようになります。

```
.
└── invoices/
    ├── [id]/
    │   └── edit/
    │       └── page.tsx
    ├── create/
    │   └── page.tsx
    └── page.tsx
```

上の構成にして、`edit`配下の`page.tsx`にアクセスしたい場合は、

`/invoices/1/edit`でアクセスできます。

`1`の部分は何でもいいです。

--- 

以下は`<Table />`コンポーネントの一部です。

この中で`<UpdateInvoice />`コンポーネントに`id`が渡されています。

```diff_tsx:/app/ui/invoices/table.tsx
export default async function InvoicesTable({
  query,
  currentPage,
}: {
  query: string;
  currentPage: number;
}) {
  return (
    // ...
    <td className="flex justify-end gap-2 whitespace-nowrap px-6 py-4 text-sm">
+     <UpdateInvoice id={invoice.id} />
      <DeleteInvoice id={invoice.id} />
    </td>
    // ...
  );
}
```

その`<UpdateInvoice />`コンポーネント内では、`id`を受けとるようにします。

受けとった`id`をNext.jsが提供している`<Link />`の`href`に

{`/dashboard/invoices/${id}/edit`}

という形で渡します。

これで編集ページにアクセスできるようになりました。

```diff_tsx:/app/ui/invoices/buttons.tsx
import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
 
// ...
 
export function UpdateInvoice({ id }: { id: string }) {
  return (
    <Link
      href={`/dashboard/invoices/${id}/edit`}
      className="rounded-md border p-2 hover:bg-gray-100"
    >
      <PencilIcon className="w-5" />
    </Link>
  );
}
```

# 2. idを取得する

先ほど`<Link />`の`href`に`id`を渡したので、それを`edit`ページ側で取得します。

`params.id`で`id`を取得することができます。

```diff_tsx:/app/dashboard/invoices/[id]/edit/page.tsx
import Form from '@/app/ui/invoices/edit-form';
import Breadcrumbs from '@/app/ui/invoices/breadcrumbs';
 
+export default async function Page({ params }: { params: { id: string } }) {
+ const id = params.id;
  return (
    <main>
      <Breadcrumbs
        breadcrumbs={[
          { label: 'Invoices', href: '/dashboard/invoices' },
          {
            label: 'Edit Invoice',
            href: `/dashboard/invoices/${id}/edit`,
            active: true,
          },
        ]}
      />
      <Form />
    </main>
  );
}
```

# 3. データベースから特定のデータを取得する

データを更新するためには元のデータの情報が必要になります。

先ほど取得した`id`を使ってデータを取得し`<Form />`コンポーネントに渡しましょう。

```diff_tsx:/dashboard/invoices/[id]/edit/page.tsx
import Form from '@/app/ui/invoices/edit-form';
import Breadcrumbs from '@/app/ui/invoices/breadcrumbs';
+import { fetchInvoiceById, fetchCustomers } from '@/app/lib/data';
 
export default async function Page({ params }: { params: { id: string } }) {
  const id = params.id;
+ const [invoice, customers] = await Promise.all([
+   fetchInvoiceById(id),
+   fetchCustomers(),
+ ]);
  // ...
}
```

これで事前に内容が入力されているフォームが完成しました。

# 4. データを更新する

最後に、フォームの`updateInvoice`関数に`id`を渡して、発火させたいのですが、以下のように渡すことはできません。

なんで渡せないかはうまく説明できません。

```diff_tsx:/app/ui/invoices/edit-form.tsx
<form action={updateInvoice(id)}>
```

代わりに、`JavaScript`の`bind`を使用することができます。

```diff_tsx:/app/ui/invoices/edit-form.tsx
// ...
+import { updateInvoice } from '@/app/lib/actions';
 
export default function EditInvoiceForm({
  invoice,
  customers,
}: {
  invoice: InvoiceForm;
  customers: CustomerField[];
}) {
+ const updateInvoiceWithId = updateInvoice.bind(null, invoice.id);
 
  return (
+   <form action={updateInvoiceWithId}>
      <input type="hidden" name="id" value={invoice.id} />
    </form>
  );
}
```

`updateInvoice`関数は、`id`と`formData`を受け取る関数です。

その`id`の部分だけ先に`bind`を使って指定しているみたいです。

第１引数に`null`を指定している理由はよく分からなかったので下のサイトをご覧ください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

---

次に`actions.ts`に`updateInvoice`関数を記述します。

```/app/lib/actions.ts
// Use Zod to update the expected types
const UpdateInvoice = FormSchema.omit({ id: true, date: true });
 
// ...
 
export async function updateInvoice(id: string, formData: FormData) {
  const { customerId, amount, status } = UpdateInvoice.parse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  });
 
  const amountInCents = amount * 100;
 
  await sql`
    UPDATE invoices
    SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
    WHERE id = ${id}
  `;
 
  revalidatePath('/dashboard/invoices');
  redirect('/dashboard/invoices');
}
```

流れとしては、

1. `formData`からデータを抽出する
1. `Zod`を使用して型を検証する
1. 金額をセントに変換する
1. 変数を SQL クエリに渡す
1. `revalidatePath`でキャッシュをクリアし、再度サーバーにリクエストする
1. `redirect`でユーザーを請求書のページにリダイレクトさせる

以上！

# おわりに

次は削除です

# 次の記事

https://qiita.com/naoyuki2/items/487239ef9c54c5ef7875