React warns when an input element has both onChange and onInput event handlers because they serve the same purpose and can cause duplicate event handling. In React, onChange handles text input changes, making onInput redundant. Removing the duplicate handler simplifies code and eliminates the warning.
React fires both onChange and onInput events when an input's value changes, but these handlers are often used redundantly. In React's virtual DOM, both onChange and onInput are automatically normalized to the same internal event type. When you attach both handlers to the same input element, React processes both, potentially executing the same logic twice or causing confusing behavior. This warning exists to help developers avoid unnecessary complexity and performance issues from duplicate event handlers. In vanilla JavaScript, onChange fires when focus is lost, while onInput fires immediately on each keystroke. However, React abstracts this difference and treats onChange as the standard way to handle input value changes across all input types. By attaching both, you're likely doing redundant work and may end up with bugs where logic accidentally runs twice.
First, determine whether you need onInput or onChange. In React, onChange is the standard and recommended approach for input value changes. If you're listening for real-time input changes (every keystroke), onChange is correct. Remove onInput entirely.
// ❌ WRONG - both handlers attached
function InputForm() {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
const handleInput = (e) => {
setValue(e.target.value);
};
return (
<input
value={value}
onChange={handleChange}
onInput={handleInput} // Duplicate!
/>
);
}Delete the onInput handler from your input element and keep only onChange. In React, onChange handles all input value changes in real-time, so onInput is redundant.
// ✅ CORRECT - only onChange
function InputForm() {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<input
value={value}
onChange={handleChange}
/>
);
}If your onChange and onInput handlers were executing different logic, combine that logic into a single handler:
// ❌ WRONG - split logic
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleChange = (e) => {
const val = e.target.value;
setQuery(val);
};
const handleInput = (e) => {
const val = e.target.value;
// Perform search
setResults(performSearch(val));
};
return (
<input
value={query}
onChange={handleChange}
onInput={handleInput} // Duplicate logic
/>
);
}
// ✅ CORRECT - combined logic
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleChange = (e) => {
const val = e.target.value;
setQuery(val);
setResults(performSearch(val));
};
return (
<input
value={query}
onChange={handleChange}
/>
);
}If you're using a custom input component or third-party UI library, verify that the wrapper doesn't already attach onInput. You may only need to provide your own onChange:
// Custom input wrapper that handles events
function CustomInput({ onChange, onInput, ...props }) {
// ❌ If component internally attaches onInput, don't pass onInput from parent
return (
<input
onChange={onChange}
{...props}
/>
);
}
// Usage - only provide onChange
function MyForm() {
const [value, setValue] = useState('');
return (
<CustomInput
value={value}
onChange={(e) => setValue(e.target.value)}
// Don't provide onInput
/>
);
}Check the third-party library documentation to understand what events it handles internally.
After removing onInput, test that your input still responds correctly to user typing:
1. Type in the input field and verify characters appear
2. Open the browser console (F12) and confirm no warning appears
3. Check that state updates happen once per keystroke (not twice)
4. If you have event handlers triggering side effects (like validation or search), confirm they run the expected number of times
5. Use React Developer Tools to inspect the component and verify handlers are being called correctly
You can temporarily add logging to confirm handlers fire once:
const handleChange = (e) => {
console.log('onChange fired', e.target.value); // Should log once per keystroke
setValue(e.target.value);
};Use your editor's find-in-files feature to search for similar patterns:
# Search for other instances of both onChange and onInput
grep -r "onChange.*onInput" src/
grep -r "onInput.*onChange" src/Or use your IDE's regex search to find input elements with both handlers:
- Pattern: onChange[^>]*onInput|onInput[^>]*onChange
- In Visual Studio Code, use Ctrl+Shift+F (Find in Files) with regex enabled
Fix any other occurrences following the same pattern.
React's event system includes normalization between browser standards and cross-browser compatibility. Historically, browsers had inconsistent behavior: onChange fired only on blur in older versions, while onInput fired on every keystroke. React standardized this by making onChange behave like onInput (firing in real-time). If you need to support very old browsers (pre-2010), consult your browser compatibility requirements, but modern React applications should never need onInput.
For controlled components (where React state drives the input value), onChange is the only correct handler. For uncontrolled components (where the DOM maintains state), you typically wouldn't use either handler unless you need to react to changes. Some developers confuse ref-based uncontrolled inputs with needing onInput; in fact, uncontrolled inputs rarely need event handlers at all.
If you're implementing a complex input component that wraps a native input, ensure your wrapper exposes only the onChange handler to consumers and manage any internal event handling within the wrapper without exposing onInput to the outside world.
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