Chapter 14 - Improve validation

This commit is contained in:
2024-01-04 08:00:38 +01:00
parent 1c23830272
commit 69f0e86915
3 changed files with 52 additions and 15 deletions

View File

@@ -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];

View File

@@ -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 (
<form action={createInvoice}>
<form action={dispatch}>
<div className="rounded-md bg-gray-50 p-4 md:p-6">
{/* Customer Name */}
<div className="mb-4">
@@ -20,21 +25,31 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
</label>
<div className="relative">
<select
id="customer"
name="customerId"
className="peer block w-full cursor-pointer rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
defaultValue=""
id="customer"
name="customerId"
className="peer block w-full cursor-pointer rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
defaultValue=""
aria-describedby="customer-error"
>
<option value="" disabled>
Select a customer
</option>
{customers.map((customer) => (
<option key={customer.id} value={customer.id}>
{customer.name}
</option>
<option key={customer.id} value={customer.id}>
{customer.name}
</option>
))}
</select>
<UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
<UserCircleIcon
className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500"/>
</div>
<div id="customer-error" aria-live="polite" aria-atomic="true">
{state.errors?.customerId &&
state.errors.customerId.map((error: string) => (
<p className="mt-2 text-sm text-red-500" key={error}>
{error}
</p>
))}
</div>
</div>