DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

Fixing “Uncontrolled   Controlled” Input Warnings in React 19

Fixing “Uncontrolled → Controlled” Input Warnings in React 19

🛑 Fixing “Uncontrolled → Controlled” Input Warnings in React 19

A deep dive into controlled vs uncontrolled inputs, why the warning fires, and bullet‑proof patterns for enterprise‑grade forms.


The Infamous 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…
Enter fullscreen mode Exit fullscreen mode

Translation

React saw an <input> without a value prop on the first render (uncontrolled).

On a later render it did receive value={something} (controlled).

React doesn’t know which behaviour you want, so it complains.


1 Controlled vs Uncontrolled in 60 s

Behaviour What it means Typical JSX
Uncontrolled DOM keeps its own state; React reads via refs / defaultValue <input defaultValue="Alice" />
Controlled React state is the single source of truth <input value={name} onChange={e => setName(e.target.value)} />

🧪 Litmus test: Does the input’s value live in React state? If yes ⇒ controlled.


2 Why the Warning Happens

export const SearchBar = () => {
  const [term, setTerm] = useState<string>(); // undefined on first render

  return (
    <input
      placeholder="Search"
      value={term}                         // 🔥 undefined → "a"
      onChange={e => setTerm(e.target.value)}
    />
  );
};
Enter fullscreen mode Exit fullscreen mode
  • Render #1: term is undefined → React omits value → uncontrolled.
  • Render #2: user types → term is "a" → React supplies value → controlled.

Hence the warning.


3 Four Production‑Grade Fixes

3.1 Provide an Initial Value

const [term, setTerm] = useState('');
Enter fullscreen mode Exit fullscreen mode

Keeps the input controlled from the start.


3.2 Flip to Uncontrolled

const inputRef = useRef<HTMLInputElement>(null);

return <input ref={inputRef} defaultValue="Alice" />;
Enter fullscreen mode Exit fullscreen mode

Read inputRef.current?.value on submit.


3.3 Guard the value Prop

<input
  value={term ?? ''}           // fallback
  onChange={}
/>
Enter fullscreen mode Exit fullscreen mode

3.4 Conditional Rendering

return term === undefined ? (
  <input defaultValue="Alice" />
) : (
  <input value={term} onChange={} />
);
Enter fullscreen mode Exit fullscreen mode

4 TypeScript Patterns

4.1 Explicit “Maybe‑Controlled” Types

type Controlled<T>   = { value: T; onChange(v: T): void };
type Uncontrolled<T> = { defaultValue?: T };

type InputProps<T> = Controlled<T> | Uncontrolled<T>;
Enter fullscreen mode Exit fullscreen mode

Forces callers to pick one.

4.2 Generic Hook Helper

function useControlled<V>(
  initial: V | undefined
): [V, Dispatch<SetStateAction<V>>, boolean] {
  const [state, setState] = useState(initial);
  const controlled = initial !== undefined;
  return [state, setState, controlled];
}
Enter fullscreen mode Exit fullscreen mode

5 Form Libraries at a Glance

Library Default Mode Notes
React Hook Form Uncontrolled (refs) Tiny, blazing fast
Formik Controlled Tons of helpers
Remix Form Native HTML Server‑centric

Choose the one that matches your input philosophy.


6 Ship‑Ready Checklist

✅ Item Why
Initialise state to '', 0, false, etc. Prevent undefined drift
Never mix defaultValue and value React considers the input controlled
Convert type number inputs e.target.value is a string
Memoise onChange with useCallback Reduce pointless renders

7 Real‑World Example (SearchBar)

import { useState, ChangeEvent } from 'react';

export function SearchBar() {
  const [term, setTerm] = useState('');

  const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
    setTerm(e.target.value);

  return (
    <input
      type="search"
      placeholder="Search GIFs"
      value={term}
      onChange={handleChange}
      className="search-input"
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

No warnings. 100 % controlled. Editor auto‑completion intact.


Final Thoughts

The warning isn’t a bug; it’s React pushing you toward predictability.

Pick one paradigm—controlled or uncontrolled—for each input’s lifetime and stick with it. Your console, tests, and future teammates will thank you.

Happy typing, and may your forms compile in peace! 📝✨

Top comments (0)