From 341ffe34c3ca8a7b419e57d424a21809d02cd694 Mon Sep 17 00:00:00 2001 From: tenk-laptop Date: Wed, 24 Jan 2024 18:12:39 +0900 Subject: [PATCH] save: ch23 --- takuo/chapter23/readme.md | 148 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/takuo/chapter23/readme.md b/takuo/chapter23/readme.md index 7d7146f..21d8ce1 100644 --- a/takuo/chapter23/readme.md +++ b/takuo/chapter23/readme.md @@ -1,2 +1,150 @@ # chapter23 (Next/13th) ## エラーハンドリング +前のチャプターでは,サーバーアクションを使用してデータを変更する方法を学びました.ここではJavaScriptの`try/catch`構文やNext.jsのAPIを使用してエラーを適切に処理する方法を学びます. + +- `error.tsx`を使用してrouteセグメント内のエラーを取得し,代わりにフォールバックUIを表示します. +- `notFound`関数の使い方と,404エラーを処理するための`not-found`ファイル + +### サーバーアクションに`try/catch`を追加する +エラーの処理を適切に処理するために,JavaScriptの`try/catch`構文をサーバーアクションに追加します. + +
+/app/lib/actions.ts + +```tsx +export async function createInvoice(formData: FormData) { + const { customerId, amount, status } = CreateInvoice.parse({ + customerId: formData.get('customerId'), + amount: formData.get('amount'), + status: formData.get('status'), + }); + + const amountInCents = amount * 100; + const date = new Date().toISOString().split('T')[0]; + + try { + await sql` + INSERT INTO invoices (customer_id, amount, status, date) + VALUES (${customerId}, ${amountInCents}, ${status}, ${date}) + `; + } catch (error) { + return { + message: 'Database Error: Failed to Create Invoice.', + }; + } + + revalidatePath('/dashboard/invoices'); + redirect('/dashboard/invoices'); +} +``` + +```tsx +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; + + try { + await sql` + UPDATE invoices + SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status} + WHERE id = ${id} + `; + } catch (error) { + return { message: 'Database Error: Failed to Update Invoice.' }; + } + + revalidatePath('/dashboard/invoices'); + redirect('/dashboard/invoices'); +} +``` + +```tsx +export async function deleteInvoice(id: string) { + try { + await sql`DELETE FROM invoices WHERE id = ${id}`; + revalidatePath('/dashboard/invoices'); + return { message: 'Deleted Invoice.' }; + } catch (error) { + return { message: 'Database Error: Failed to Delete Invoice.' }; + } +} +``` + +
+ +ここで注目すべきなのは,`redirect`が`try/catch`の外で呼ばれていることです.これは`redirect`がerrorを発出するためで,`try`句の中で`redirect`するとそれが`catch`されてしまいます.そのため,`try/catch`の後で`redirect`することで,`try`句の処理が成功した場合に`redirect`が実行されるようになります. + +### `error.tsx`でエラーを処理する +`error.tsx`はrouteセグメントのUI境界を定義するために使用できます.これは**予期しないエラー**に対するハンドリングとして機能し,ユーザーにフォールバックUIを表示します. +`/dashboard/invoices/error.tsx`を作成し,次のようにします. + +```tsx +'use client'; + +import { useEffect } from 'react'; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + // Optionally log the error to an error reporting service + console.error(error); + }, [error]); + + return ( +
+

Something went wrong!

+ +
+ ); +} +``` +`error.tsx`について, +- `error.tsx`はクライアントコンポーネントである必要があります(`"use client"`). +- 2つのpropsをとります + - `error`: JavaScriptのErrorオブジェクトのインスタンス + - `reset`: エラー境界をリセットするための関数で,実行するとrouteセグメントの再レンダリングを試みます. + +### `notFound`関数で404エラーを処理する +`notFound`関数を使用することでもエラーハンドリングが可能です.`error.tsx`は**全ての**エラーを補足するのに便利ですが,`notFound`は存在しないリソースを取得しようとするときにも使用できます. + +たとえば,存在しないUUIDの請求書を編集しようとすると +- http://localhost:3000/dashboard/invoices/2e94d1ed-d220-449f-9f11-f0bbceed9645/edit + +`error.tsx`のエラーが表示されます(error.tsxの子要素なので). + +ただ,もっとエラーを具体的にしたい場合は,404エラーを表示して,アクセスしようとしているリソースが見つからなかったことをユーザに示すことができます.見つからなかったことを確認するには,`data.ts`の`fetchInvoiceById`関数にて,`invoice`をログ出力するようにします. + +```diff tsx + export async function fetchInvoiceById(id: string) { + noStore(); + try { + // ... + ++ console.log(invoice); // Invoice is an empty array [] + return invoice[0]; + } catch (error) { + console.error('Database Error:', error); + throw new Error('Failed to fetch invoice.'); + } + } +``` +invoiceの返り値が空だった時は`notFound`関数を呼び出すようにします. +