Skip to Content

Common Form Cases & Solutions

Uncontrolled to Controlled Input Warning

The Warning

You may encounter this warning in React:

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…

Why This Happens

This warning occurs when an input’s value prop changes from undefined to a defined value (or vice versa). React distinguishes between:

  • Controlled inputs: Have a value prop that is always defined (even if it’s an empty string)
  • Uncontrolled inputs: Have value={undefined} or no value prop at all

When React Hook Form initializes, if defaultValues are not set or if a field’s value starts as undefined, the input is uncontrolled. Once the value becomes defined, React tries to convert it to controlled, which triggers the warning.

Solutions

Solution 1: Fix in Input Component (Shadcn UI)

If you have direct access to the input component (like in Shadcn UI), you can fix it at the component level by ensuring the value is always defined:

// components/ui/input.tsx export const Input = ({ value, ...props }) => { return <input value={value || ''} {...props} />; };

This ensures the value prop is always a string (never undefined), making the input always controlled.

Solution 2: Use defaultValues (External Libraries)

If you don’t have direct access to the input element (like when using Chakra UI’s Input component or other third-party libraries), you should always provide defaultValues in useForm:

import { useForm } from "react-hook-form"; import { Input } from "@chakra-ui/react"; const form = useForm({ defaultValues: { username: "", email: "", // ... all fields should have default values }, });

Note: We’ll discuss init methods from utility files later, which can help standardize default values across your application.


Edit Mode: Populating Form with Server Data

When opening a form in edit mode, you need to populate the form fields with data fetched from the server. React Hook Form provides the reset() method for this purpose.

The Problem

If you try to set defaultValues with async data, it won’t work because useForm is called synchronously, but data fetching is asynchronous:

// ❌ This won't work const { data } = useQuery(...); const form = useForm({ defaultValues: data, // data is undefined initially });

The Solution: Use reset() in useEffect

After fetching the data, call form.reset() with the fetched data:

import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { useQuery } from "@tanstack/react-query"; export function EditUserForm({ userId }: { userId: string }) { const form = useForm({ defaultValues: { username: "", email: "", // ... initial empty values }, }); // Fetch data from server const { data: userData, isLoading } = useQuery({ queryKey: ["user", userId], queryFn: () => fetchUser(userId), }); // Populate form when data is fetched useEffect(() => { if (userData) { form.reset(userData); // This updates all form fields with the fetched data } }, [userData, form]); const onSubmit = (data: FormValues) => { // Handle form submission }; if (isLoading) return <div>Loading...</div>; return ( <form onSubmit={form.handleSubmit(onSubmit)}> {/* Your form fields */} </form> ); }

How reset() Works

The reset() method:

  • Updates all form field values
  • Resets the form’s validation state
  • Marks the form as “pristine” (not dirty)
  • Can optionally accept new default values for future resets

Best Practices

  1. Always provide initial defaultValues even if they’re empty, to avoid the uncontrolled input warning
  2. Use reset() after data fetching to populate the form with server data
  3. Handle loading states to prevent form submission before data is loaded
  4. Reset on cleanup if needed when the component unmounts or the ID changes
useEffect(() => { if (userData) { form.reset(userData); } // Cleanup: reset form when userId changes return () => { form.reset(); }; }, [userData, userId, form]);

Summary

  • Uncontrolled Input Warning: Always provide defaultValues or ensure input components handle undefined values
  • Edit Mode: Use form.reset(fetchedData) in a useEffect after data is fetched from the server
  • Best Practice: Initialize forms with empty default values, then populate with reset() when data is available