ui: React error "uncontrolled input" in forms (demo repo provided)

Hi again Shadcn and congrats for continuing to improve your already awesome project!

I noticed that React throws the error A component is changing an uncontrolled input to be controlled when we start typing in a form input:

image

Here is a fresh Next.js project to which I added the example form provided in the doc: https://github.com/Zwyx/shadcn-ui-form-uncontrolled-input-demo

Simply start the project, open your browser’s console, and type in the form. You should see the error being thrown.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 11
  • Comments: 24 (4 by maintainers)

Most upvoted comments

So, since FormField is using a controlled component, you need to provide a default value for it. Otherwise React is seeing it going from undefined to a value.

const form = useForm<z.infer<typeof formSchema>>({
  resolver: zodResolver(formSchema),
  defaultValues: {
+    username: "",
  },
})

From the docs:

Important: Can not apply undefined to defaultValue or defaultValues at useForm.You need to either set defaultValue at the field-level or useForm’s defaultValues. undefined is not a valid value.If your form will invoke reset with default values, you will need to provide useForm with defaultValues.

(Edit: I’ve update the example in the docs)

Thanks for this @tarikyildizci, helps get around the default values tripping zod for required inputs. This must be a very common problem, it’s the most basic implementation of a form, this should have more attention imo.

<Input {...field} type="number" min={0} value={field.value ?? ""} />

this seems to resolve this issue, hook-form and zod still see the field.value, which is undefined, but the component recieves an empty string instead of undefined.

@Wouter8 you can set a number as default value.

@dan5py I don’t want to set an arbitrary default value like 0…

I have a number input, so I can’t set the default value to an empty string. Any workaround for this?

I don’t really understand this behavior. I think it’s counter-intuitive to have a default value as a must in this case. I have a form and a select with multiple choices inside and I want to show the placeholder by default and not a selected value. Let’s say the selector is gender and "male", "female" … . I don’t want to specify a gender by default. But as my Form is used with my type formSchema a null value or empty string would cause a typescript error. And accepting that in my validator would cause my validation to allow null values which I don’t want. So to get rid of it I would need to set an enum value from gender, so “male” as a default value? How are you getting around this issue? Am I missing something?

I’ve implemented numbers in Zod using the following:

function requiredNumberTransform(num: number | "", ctx: z.RefinementCtx) {
    if (num === "") {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: "Enter a number",
            fatal: true,
        });

        return z.NEVER;
    }

    return num;
}

const OptionalNumber = z.number().or(z.literal(""));
const RequiredNumber = OptionalNumber.transform(requiredNumberTransform);

This works with z.input<typeof schema> and z.output<typeof schema> and the number fields will be properly typed. You can pass an empty string as a default value, and the form’s submission type will be number for RequiredNumber or number | "" for OptionalNumber.

<Input {...field} type="number" min={0} value={field.value ?? ""} /> this seems to resolve this issue, hook-form and zod still see the field.value, which is undefined, but the component recieves an empty string instead of undefined.

What are your default values?

for hook-form, undefined, but the input is tricked into it being an empty string

So, since FormField is using a controlled component, you need to provide a default value for it. Otherwise React is seeing it going from undefined to a value.

const form = useForm<z.infer<typeof formSchema>>({
  resolver: zodResolver(formSchema),
  defaultValues: {
+    username: "",
  },
})

From the docs:

Important: Can not apply undefined to defaultValue or defaultValues at useForm.You need to either set defaultValue at the field-level or useForm’s defaultValues. undefined is not a valid value.If your form will invoke reset with default values, you will need to provide useForm with defaultValues.

(Edit: I’ve update the example in the docs)

And required_error of z.string({required_error: "xxxx"}) will only be triggered if the default is undefined

I used it to trigger required_error

onChange={(event) => {
 field.onChange(event.target.value || undefined)
}}