From 69f0e86915abc2065dd34cd96161183162bbfbfc Mon Sep 17 00:00:00 2001 From: Jakub Knetl Date: Thu, 4 Jan 2024 08:00:38 +0100 Subject: [PATCH] Chapter 14 - Improve validation --- app/lib/actions.ts | 31 ++++++++++++++++++++++++++----- app/ui/invoices/create-form.tsx | 33 ++++++++++++++++++++++++--------- package.json | 3 ++- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/app/lib/actions.ts b/app/lib/actions.ts index 27f2a16..9a82aee 100644 --- a/app/lib/actions.ts +++ b/app/lib/actions.ts @@ -7,9 +7,11 @@ import {redirect} from "next/navigation"; const FormSchema = z.object({ id: z.string(), - customerId: z.string(), - amount: z.coerce.number(), - status: z.enum(['pending', 'paid']), + customerId: z.string({ + invalid_type_error: 'Please select a customer.', + }), + amount: z.coerce.number().gt(0, { message: 'Please enter an amount greater than $0.' }), + status: z.enum(['pending', 'paid'], { invalid_type_error: 'Please select an invoice status'}), date: z.string(), }); @@ -17,14 +19,33 @@ const UpdateInvoice = FormSchema.omit({ id: true, date: true }); const CreateInvoice = FormSchema.omit({ id: true, date: true }); -export async function createInvoice(formData: FormData) { +export type State = { + errors?: { + customerId?: string[]; + amount?: string[]; + status?: string[]; + }; + message?: string | null; +}; - const { customerId, amount, status } = CreateInvoice.parse({ +export async function createInvoice(prevState: State, formData: FormData) { + + const validatedFields = CreateInvoice.safeParse({ customerId: formData.get('customerId'), amount: formData.get('amount'), status: formData.get('status'), }); + // If form validation fails, return errors early. Otherwise, continue. + if (!validatedFields.success) { + return { + errors: validatedFields.error.flatten().fieldErrors, + message: 'Missing Fields. Failed to Create Invoice.', + }; + } + + const { customerId, amount, status } = validatedFields.data; + const amountInCents = amount * 100; const date = new Date().toISOString().split('T')[0]; diff --git a/app/ui/invoices/create-form.tsx b/app/ui/invoices/create-form.tsx index 8882ac5..6492feb 100644 --- a/app/ui/invoices/create-form.tsx +++ b/app/ui/invoices/create-form.tsx @@ -1,3 +1,4 @@ +'use client'; import { CustomerField } from '@/app/lib/definitions'; import Link from 'next/link'; import { @@ -8,10 +9,14 @@ import { } from '@heroicons/react/24/outline'; import { Button } from '@/app/ui/button'; import {createInvoice} from "@/app/lib/actions"; +import { useFormState } from 'react-dom'; export default function Form({ customers }: { customers: CustomerField[] }) { + const initialState = { message: null, errors: {} }; + const [state, dispatch] = useFormState(createInvoice, initialState); + console.log(state); return ( -
+
{/* Customer Name */}
@@ -20,21 +25,31 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
- + +
+
+ {state.errors?.customerId && + state.errors.customerId.map((error: string) => ( +

+ {error} +

+ ))}
diff --git a/package.json b/package.json index 58d936d..361f1fc 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "prettier": "prettier --write --ignore-unknown .", "prettier:check": "prettier --check --ignore-unknown .", "start": "next start", - "seed": "node -r dotenv/config ./scripts/seed.js" + "seed": "node -r dotenv/config ./scripts/seed.js", + "lint": "next lint" }, "dependencies": { "@heroicons/react": "^2.0.18",