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.

    Sign up

    '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>
        )
    }