Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shadcn Input + Form + Zod "A component is changing an uncontrolled input to be controlled"

A little background

I'm using Shadcn UI library and Zod to create some simple forms in my Next.js React app. The inputs are of type select and text.

The problem

I'm following Shadcn documentation on how to set up the form with Zod and Shadcn's ui components but I get an error when the value of the text input is changing:

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.

Worth mentioning

The error only occurs when changing the text (username) input value.

Also, the form is working properly despite the error.

What I've tried

Ive looked up the error and I found answers saying to add a value or defaultValue after the {...field} with a combination of a useState('') hook, which solved the error but now it won't let me submit the form.

Code

'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 {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { toast } from "@/components/ui/use-toast"
import Link from "next/link"
import { Input } from "@/components/ui/input"
import { ReactNode, useState } from "react"
const FormSchema = z.object({
  email: z
    .string({
      required_error: "Please select an email to display.",
    })
    .email(),
    username: z.string(),
 
})

const ZodForm = () => {

  const form = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
  })
  
  function onSubmit(data: z.infer<typeof FormSchema>) {
    console.log(data);
    
    toast({
      title: "You submitted the following values:",
      description: (
        <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
          <code className="text-white">{JSON.stringify(data, null, 2)}</code>
        </pre>
      ),
    })
  }


  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <Select onValueChange={field.onChange} defaultValue={field.value}>
                <FormControl>
                  <SelectTrigger>
                    <SelectValue placeholder="Select a verified email to display" />
                  </SelectTrigger>
                </FormControl>
                <SelectContent>
                  <SelectItem value="[email protected]">[email protected]</SelectItem>
                  <SelectItem value="[email protected]">[email protected]</SelectItem>
                  <SelectItem value="[email protected]">[email protected]</SelectItem>
                </SelectContent>
              </Select>
              <FormDescription>
                You can manage email addresses in your{" "}
                <Link href="/examples/forms">email settings</Link>.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <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>
          )}
        />
        <button type="submit">Submit</button>
      </form>
    </Form>
  )
}

export default ZodForm
like image 929
IdoBa Avatar asked Nov 20 '25 07:11

IdoBa


2 Answers

That occurs because you didn't set defaultValues to useForm.

So, the defaultValues is undefined.

then When You use the <FormField /> You probably write it like this:

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

Notice this line <Input {...field} />

The field is an object of ref, value, onChange, onBlur, disabled, and name.

The problem is the value is initialized by undefined then when you write any letter It's changed from undefined to string.

You can easily solve that by setting the default values

useForm({
  defaultValues: {
    username: ""
  }
})
like image 165
Ahmed Abdelbaset Avatar answered Nov 21 '25 21:11

Ahmed Abdelbaset


I was facing the problem like you and I resolved the problem by following the few steps:

  1. Setting the default values first.
const form = useForm({
   //...
   mode: 'onChange',
   defaultValues: initialValue,
});
  1. Load the data from API.
  useEffect(() => {
    if (props.data.id) {
      getData(props.data.id).then((data) => {
        form.reset(data);
        setData(data);
      });
    }
  }, [props.data.id, form]);

Hope that help.

like image 39
Em Ha Tuan Avatar answered Nov 21 '25 22:11

Em Ha Tuan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!