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