React warns when you provide a value prop to a form field without an onChange handler. This creates a read-only field that cannot be edited. Fix it by adding an onChange handler, using defaultValue instead, or explicitly setting the readOnly prop.
This warning occurs when you set the value prop on an input element without providing an onChange event handler. In React, controlled components require both a value and an onChange handler to function properly. When you pass a value prop, React expects you to manage that value through state and update it via onChange events. Without an onChange handler, the input becomes read-only because React has no way to update the controlled value when users type. This creates a confusing user experience where the input appears editable but doesn't respond to user input. React displays this warning to help you identify the issue before users encounter the broken behavior. This warning is distinct from the "uncontrolled to controlled" warning, which occurs when an input switches between having undefined/null values and defined values. The "controlled without onChange" warning specifically targets inputs that have a defined value but lack the change handler needed to update that value.
The standard solution is to add an onChange handler that updates the state backing the input value. This creates a proper controlled component:
import { useState } from 'react';
function MyForm() {
const [email, setEmail] = useState('');
return (
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);
}The onChange handler receives a synthetic event, and e.target.value contains the new input value. Update your state synchronously in the handler so React re-renders with the new value.
If you don't need to control the input value through React state, use defaultValue instead of value. This creates an uncontrolled component that manages its own state:
function MyForm() {
return (
<input
type="text"
defaultValue="Initial text"
name="username"
/>
);
}With defaultValue, React sets the initial value but doesn't control subsequent changes. Access the value later via a ref or form submission event. This approach works well for simple forms where you don't need real-time validation or derived state.
If the field genuinely should not be editable, add the readOnly prop to explicitly declare this intent and suppress the warning:
function UserProfile({ username }) {
return (
<input
type="text"
value={username}
readOnly
className="bg-gray-100"
/>
);
}The readOnly attribute makes the semantic purpose clear and tells React you intentionally want a non-editable field. Consider using styled text or a <p> tag instead if no input interaction is needed.
Ensure your state is never undefined or null if you're using controlled components. Always initialize with an appropriate default:
// Good - always defined
const [text, setText] = useState('');
const [count, setCount] = useState(0);
const [selected, setSelected] = useState('option1');
// Bad - undefined causes issues
const [text, setText] = useState();
const [data, setData] = useState(null); // If used immediatelyFor values that load asynchronously, use empty string, empty array, or provide a loading state until data arrives. Use the value ?? '' pattern if the value might be undefined or null.
Verify you're not conditionally setting onChange in a way that causes it to be undefined:
// Problem - onChange only set sometimes
<input
value={text}
{...(someCondition && { onChange: handleChange })}
/>
// Fix - always provide onChange or use readOnly
<input
value={text}
onChange={someCondition ? handleChange : () => {}}
readOnly={!someCondition}
/>Inspect any prop spreading or conditional logic that might cause onChange to be missing. Ensure the handler is always present for controlled inputs.
If using form libraries like React Hook Form, Formik, or React Bootstrap Typeahead, ensure you're passing the onChange handler correctly:
// React Hook Form example
import { useForm } from 'react-hook-form';
function MyForm() {
const { register } = useForm();
return (
<input
type="text"
{...register('fieldName')} // Spreads value, onChange, etc.
/>
);
}Consult the library's documentation for the correct pattern. Some libraries provide custom onChange prop names or handle state management differently. Check GitHub issues for known compatibility problems.
React's controlled component pattern ensures the component state is the "single source of truth" for form values. This enables features like real-time validation, conditional logic, and synchronized multi-field behavior, but requires proper onChange handling.
For complex forms with many fields, consider using a form management library like React Hook Form or Formik that handles the onChange boilerplate automatically. These libraries use refs and uncontrolled components internally for better performance while providing a controlled-like API.
The warning fires during development but is suppressed in production builds. However, the underlying issue (non-functional inputs) persists, so always fix the warning during development.
If you need a hybrid approach, you can start with an uncontrolled component using defaultValue and later "upgrade" to a controlled component by adding state and onChange when you need more control. Just avoid switching back and forth during a component's lifecycle.
For accessibility, read-only inputs should still be keyboard-accessible and properly labeled. Consider using aria-readonly and ensure screen readers announce the read-only state correctly.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite