This React warning appears when you set a checked prop on a checkbox or radio button without providing an onChange event handler. React treats inputs with a checked prop as controlled components, which require onChange handlers to update their state.
This warning occurs when React detects a checkbox or radio button with a checked prop but no corresponding onChange handler. In React's component model, form inputs can be either controlled or uncontrolled. When you provide a checked prop with a boolean value, React treats the input as a controlled component, meaning React is responsible for managing its state. Controlled components require an onChange handler to update the backing state when the user interacts with the input. Without this handler, the checkbox becomes effectively read-only - users can click it, but the visual state won't change because nothing is updating the checked value. This creates a confusing user experience where the interface appears broken. React issues this warning to alert you that you've created a controlled component pattern but haven't completed the implementation by providing the state update mechanism.
If you want a controlled checkbox where React manages the state, add both state and an onChange handler:
import { useState } from 'react';
function MyComponent() {
const [isChecked, setIsChecked] = useState(false);
return (
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
);
}For radio buttons:
function RadioExample() {
const [selected, setSelected] = useState('option1');
return (
<div>
<input
type="radio"
checked={selected === 'option1'}
onChange={() => setSelected('option1')}
/>
<input
type="radio"
checked={selected === 'option2'}
onChange={() => setSelected('option2')}
/>
</div>
);
}This is the recommended approach when you need to react to changes, validate input, or synchronize with other components.
If you only need to set an initial value and don't need React to track changes, use defaultChecked instead:
function MyComponent() {
return (
<input
type="checkbox"
defaultChecked={true}
/>
);
}With defaultChecked, the browser manages the input's state internally. Access the value when needed using refs:
import { useRef } from 'react';
function FormExample() {
const checkboxRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log(checkboxRef.current.checked);
};
return (
<form onSubmit={handleSubmit}>
<input
type="checkbox"
ref={checkboxRef}
defaultChecked={false}
/>
<button type="submit">Submit</button>
</form>
);
}This approach works well for simple forms where you only need the final value.
If you want to display a checkbox state without allowing user interaction, use the readOnly prop:
function DisplayOnly({ isFeatureEnabled }) {
return (
<input
type="checkbox"
checked={isFeatureEnabled}
readOnly
/>
);
}The readOnly prop explicitly tells React this is intentionally non-interactive, which suppresses the warning. Note that readOnly inputs:
- Still submit with forms (unlike disabled inputs)
- Can receive focus
- Show the correct visual state
- Don't trigger onChange events
Alternatively, if the checkbox should be completely non-interactive and not submit with forms, use disabled:
<input
type="checkbox"
checked={value}
disabled
/>Sometimes this warning appears when state initialization allows undefined values:
// WRONG - checked becomes undefined initially
function BadExample() {
const [isChecked, setIsChecked] = useState();
return (
<input
type="checkbox"
checked={isChecked} // undefined at first, then boolean
onChange={(e) => setIsChecked(e.target.checked)}
/>
);
}Fix by providing an initial boolean value:
// CORRECT
function GoodExample() {
const [isChecked, setIsChecked] = useState(false);
return (
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
);
}If the initial value comes from props that might be undefined, use nullish coalescing:
function ComponentWithProps({ initialChecked }) {
const [isChecked, setIsChecked] = useState(initialChecked ?? false);
return (
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
);
}When to use controlled vs uncontrolled:
Controlled components are preferred when you need to:
- Validate input as the user types
- Conditionally enable/disable form submission
- Enforce specific input formats
- Synchronize the same data across multiple components
- Trigger side effects on value changes
- Implement complex form logic with interdependent fields
Uncontrolled components with defaultChecked are suitable when:
- Building simple forms that only need values on submit
- Working with large forms where re-rendering on every change is expensive
- Integrating with non-React code that expects direct DOM access
- Implementing file inputs (which must be uncontrolled)
Event handler patterns:
For multiple checkboxes with shared state, use a dynamic handler:
function MultiCheckbox() {
const [options, setOptions] = useState({
feature1: false,
feature2: true,
feature3: false,
});
const handleChange = (name) => (e) => {
setOptions(prev => ({
...prev,
[name]: e.target.checked
}));
};
return (
<>
<input type="checkbox" checked={options.feature1} onChange={handleChange('feature1')} />
<input type="checkbox" checked={options.feature2} onChange={handleChange('feature2')} />
<input type="checkbox" checked={options.feature3} onChange={handleChange('feature3')} />
</>
);
}Form libraries: Consider using form libraries like React Hook Form or Formik for complex forms. These libraries handle controlled/uncontrolled patterns automatically and provide validation, error handling, and submission logic out of the box.
TypeScript typing: When working with TypeScript, properly type your event handlers:
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setIsChecked(event.target.checked);
};React.FC expects children prop to be defined
React.FC no longer includes implicit children prop
Warning: You provided a `selected` prop to a form field without an `onChange` handler
You provided a 'selected' prop without an onChange handler
Failed to load source map from suspense chunk
How to fix "Failed to load source map from suspense chunk" in React
Prop spreading could cause security issues
Prop spreading could cause security issues
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