Submission Errors
Handle server-side submission errors and map them to fields.
Usage
Submission errors generally fall into two categories: field-specific errors and general form errors. Here’s a common approach to display both types:
- For field-specific errors, use
setSubmitErrors
. - For general form errors, simply display a toast message.
'use client'
import { useMutation } from '@tanstack/react-query'
import { useForm } from 'react-hook-form'
import { toast } from '@workspace/ui/components/Sonner'
import { z } from '@workspace/lib/validation'
import { Button } from '@workspace/ui/components/Button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
setSubmitErrors,
} from '@workspace/ui/components/Form'
import { Input } from '@workspace/ui/components/Textfield'
import { LoadingOverlay } from '@workspace/ui/components/LoadingOverlay'
interface FormValues {
email: string
name: string
}
/**
* This is a mock function that simulates an error creating a user
* It throws an error with a cause object that contains the error messages for the email and name fields
*/
const createUser = async () => {
await new Promise(resolve => setTimeout(resolve, 1000))
throw new Error('Failed to create user', {
cause: { email: 'Email already exists', name: 'Name already exists' },
})
}
export function RecFormSubmissionErrors() {
const form = useForm<FormValues>({
defaultValues: {
email: '',
name: '',
},
})
const createUserMutation = useMutation({ mutationFn: () => createUser() })
const onSubmit = (data: FormValues) => {
createUserMutation.mutate(undefined, {
onSuccess: () => {
toast.neutral({
title: 'User created successfully',
description: <code>{JSON.stringify(data)}</code>,
})
},
// we use any for testing purposes, in production you should use the correct type
onError: (error: any) => {
const cause = error.cause as Record<string, string>
// field-specific errors
setSubmitErrors(form, cause)
// general form error
toast.error({
title: error.message,
})
},
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 w-full">
<h2 className="text-xl font-semibold">Sign up</h2>
<LoadingOverlay isLoading={createUserMutation.isPending}>
<div className="grid gap-4">
<FormField
control={form.control}
name="email"
rules={{ validate: z.string().email().validateFn() }}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} placeholder="Enter your email" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name"
rules={{ validate: z.string().min(4).validateFn() }}
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} placeholder="Enter your name" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button className="w-full" type="submit">
Sign up
</Button>
</div>
</LoadingOverlay>
</form>
</Form>
)
}