Changing an uncontrolled input to be controlled

A form field started with no value and then got one, so React switched from letting the browser manage it to managing it from state.

Browser console
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. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components

Also appears as

  • A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components
  • `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
  • 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. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components

Prompt to fix it

React warns "A component is changing an uncontrolled input to be controlled." Find the input in the component stack and check what its value or checked prop is on the first render. Make it controlled from the start: initialize state with an empty string or false, give form objects every field up front, and fall back with ?? "" for nullable database fields. If the form edits existing data, render it after the data loads and reset it with a key when the record changes. Keep each input in one mode for its whole life.

Paste into Layerwise or any AI coding tool.

What it means

React form fields work in one of two modes. A controlled input gets its text from your state through value and reports every keystroke through onChange. An uncontrolled input keeps its own text in the browser, and you read it when you need it. React expects each field to pick one mode and keep it.

React treats value={undefined} and value={null} as “uncontrolled.” So when state starts as undefined and later becomes a string, for example after data loads, the field switches modes and React warns. Anything the user typed before the switch can be overwritten.

Database rows are the usual source. A nullable column comes back as null, and a missing field reads as undefined. Fall back to an empty string at the input, as in value={profile.bio ?? ''}, and the field stays controlled the whole time.

Common causes

  • State starts empty, as in useState() or useState<string>(), and gets a string once the user types or data loads.
  • A form object fills in over time, so form.email is undefined until the user edits it.
  • A nullable database column arrives as null and goes straight into value.
  • A checkbox uses checked={user?.isAdmin} while user is still loading.

How to fix it

  1. Find the input in the warning’s component stack and check what its value or checked is on the first render.
  2. Start text state with an empty string, useState(''), and give form objects every field up front.
  3. Fall back at the input for data that can be missing: value={todo.title ?? ''} and checked={todo.done ?? false}.
  4. If you don’t need each keystroke in state, make the field uncontrolled with defaultValue and read it from the form on submit.
  5. For an edit form, render it once the data arrives and give it key={record.id} so it starts fresh for each record.

Example fix

import { useState } from 'react';

type Profile = { id: string; name: string | null; newsletter: boolean | null };

// Render as <ProfileForm key={profile.id} profile={profile} /> once data loads.
export function ProfileForm({ profile }: { profile: Profile }) {
  // Strings and booleans from the start keep both inputs controlled.
  const [name, setName] = useState(profile.name ?? '');
  const [newsletter, setNewsletter] = useState(profile.newsletter ?? false);

  return (
    <form>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <input
        type="checkbox"
        checked={newsletter}
        onChange={(e) => setNewsletter(e.target.checked)}
      />
    </form>
  );
}
All errors
TypeError: Cannot read properties of undefined (reading 'map')

Cannot read properties of undefined

The code asked for a property of a value that doesn’t exist yet, usually data that hasn’t loaded or a field the API didn’t return.

Objects are not valid as a React child (found: object with keys {id, title}). If you meant to render a collection of children, use an array instead.

Objects are not valid as a React child

React was asked to show a whole object, such as a database row or a date, on the page, and it can only show text, numbers, and elements.

Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used: - A server/client branch `if (typeof window !== 'undefined')`. - Variable input such as `Date.now()` or `Math.random()` which changes each time it's called. - Date formatting in a user's locale which doesn't match the server. - External changing data without sending a snapshot of it along with the HTML. - Invalid HTML tag nesting. It can also happen if the client has a browser extension installed which messes with the HTML before React loaded. https://react.dev/link/hydration-mismatch

Hydration failed

The HTML the server sent didn’t match what React rendered first in the browser, so React threw that part away and rebuilt it on the client.

Too many re-renders. React limits the number of renders to prevent an infinite loop.

Too many re-renders

A component changes its own state while it renders, so React renders it again and again until it stops the loop.

Build with an agent that checks its work

The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.