Published on

Shadcn Input with Form and Zod: Resolving the Uncontrolled Input Warning

Authors
  • Name
    Ripal & Zalak
    Twitter

Shadcn Input with Form and Zod: Resolving the Uncontrolled Input Warning

When building forms with Shadcn UI, React Hook Form, and Zod, you might encounter this warning:

Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen.

This error occurs when the value of an input field transitions from undefined to a defined value. Although the form may still work, this warning indicates a potential issue with your code. Let’s explore the cause and how to fix it.


The Problem

In React Hook Form (RHF), fields that don’t have defaultValues start with an undefined value. When you type into such fields, the value changes from undefined to a string, triggering the warning.

Here’s an example of code that causes this issue:

<FormField
  control={form.control}
  name="username"
  render={({ field }) => (
    <FormItem>
      <FormLabel>Username</FormLabel>
      <FormControl>
        <Input placeholder="shadcn" {...field} />
      </FormControl>
      <FormDescription>This is your public display name.</FormDescription>
      <FormMessage />
    </FormItem>
  )}
/>

The field object from RHF doesn’t initialize a value, causing it to be undefined initially. The solution is to define defaultValues in your useForm configuration.


The Solution

1. Set Default Values in useForm

Adding defaultValues to your useForm configuration ensures that all fields are initialized with a value, avoiding the transition from undefined to a string.

const form = useForm({
  resolver: zodResolver(FormSchema),
  defaultValues: {
    username: '',
    email: '',
  },
})

2. Use Controlled Inputs

Ensure all inputs are properly controlled by React Hook Form. Here’s the updated implementation for the username field:

<FormField
  control={form.control}
  name="username"
  render={({ field }) => (
    <FormItem>
      <FormLabel>Username</FormLabel>
      <FormControl>
        <Input placeholder="shadcn" {...field} />
      </FormControl>
      <FormDescription>This is your public display name.</FormDescription>
      <FormMessage />
    </FormItem>
  )}
/>

With defaultValues set, the field.value is no longer undefined, preventing the warning.


3. Handling Dynamic Data

If your form data comes from an API or is dynamic, use the reset method provided by React Hook Form to initialize the form values after data is loaded.

Example:

useEffect(() => {
  if (data) {
    form.reset(data)
  }
}, [data, form])

This ensures that the form values are populated correctly when the data is available.


Complete Example

Here’s a complete implementation of a form with Shadcn UI, React Hook Form, and Zod:

'use client'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
import { Input } from '@/components/ui/input'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'
import { toast } from '@/components/ui/use-toast'

const FormSchema = z.object({
  email: z.string().email(),
  username: z.string(),
})

const ZodForm = () => {
  const form = useForm({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      email: '',
      username: '',
    },
  })

  function onSubmit(data) {
    console.log(data)
    toast({
      title: 'Form Submitted',
      description: JSON.stringify(data, null, 2),
    })
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <Select onValueChange={field.onChange} defaultValue={field.value}>
                <FormControl>
                  <SelectTrigger>
                    <SelectValue placeholder="Select your email" />
                  </SelectTrigger>
                </FormControl>
                <SelectContent>
                  <SelectItem value="[email protected]">[email protected]</SelectItem>
                  <SelectItem value="[email protected]">[email protected]</SelectItem>
                </SelectContent>
              </Select>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="username"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <Input placeholder="shadcn" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <button type="submit">Submit</button>
      </form>
    </Form>
  )
}

export default ZodForm

Conclusion

The warning about uncontrolled inputs can be easily resolved by setting defaultValues in your React Hook Form configuration. This ensures that all fields have initial values, preventing the transition from undefined to a defined value. For dynamic data, use the reset method to populate form values after data is loaded.

By following these practices, you can build robust and error-free forms using Shadcn UI, React Hook Form, and Zod.