React warns when an input element specifies both value and defaultValue props. These props represent two different approaches to managing form state: controlled (value) and uncontrolled (defaultValue) components, and cannot be mixed on the same element.
This warning occurs when React detects that you've provided both the `value` and `defaultValue` props to the same form input element. These two props represent fundamentally different approaches to managing form state in React. The `value` prop is used for controlled components, where React maintains complete control over the input's value through state. Every keystroke updates the state, and the input always displays what's in the state. The `defaultValue` prop is used for uncontrolled components, where React only sets the initial value and the browser's DOM manages the actual current value. When you specify both props, React doesn't know which pattern you want to follow. Should it treat the input as controlled (strictly managing its value) or uncontrolled (only setting an initial value)? This ambiguity prevents React from properly managing the component's behavior, so it issues this warning to alert you to make a clear choice.
Check your console for the full warning message, which identifies the specific component. Look for input, textarea, or select elements with both props:
// This causes the warning
<input value={name} defaultValue="John" />
<textarea value={bio} defaultValue="Write bio..." />
<select value={country} defaultValue="US">...</select>Use your browser's React DevTools to inspect the props of form components and verify which elements have both value and defaultValue specified.
Choose based on your requirements:
Use controlled (value prop) when you need:
- Real-time validation as users type
- To conditionally enable/disable submit buttons
- To transform input (uppercase, formatting, etc.)
- To sync the value with other parts of your UI
- Complete control over the input's state
Use uncontrolled (defaultValue prop) when you need:
- Simple forms where you only need the value on submit
- To integrate with non-React code or libraries
- To minimize re-renders for performance reasons
- Traditional form behavior where the DOM manages state
For most React applications, controlled components are recommended as they provide better integration with React's state management.
If you choose the controlled approach, remove defaultValue and manage the value with state:
// Before: Mixed props
<input value={name} defaultValue="John" onChange={handleChange} />
// After: Fully controlled
import { useState } from 'react';
function MyForm() {
const [name, setName] = useState('John'); // Initialize in state
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}Critical: Always provide an onChange handler with value, or explicitly set readOnly if you don't want user input. Ensure the state value is never undefined or null—use an empty string '' as the default:
// Correct: Always defined
const [name, setName] = useState('');
// Wrong: Can cause controlled/uncontrolled switching
const [name, setName] = useState(); // undefinedIf you choose the uncontrolled approach, remove value and use defaultValue with refs:
// Before: Mixed props
<input value={name} defaultValue="John" onChange={handleChange} />
// After: Fully uncontrolled
import { useRef } from 'react';
function MyForm() {
const nameRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log(nameRef.current.value); // Access value when needed
};
return (
<form onSubmit={handleSubmit}>
<input defaultValue="John" ref={nameRef} />
<button type="submit">Submit</button>
</form>
);
}With uncontrolled components, you typically access the value only when needed (like on form submission) rather than tracking it continuously.
If you need to set an initial value conditionally, handle it in the state initialization, not with both props:
// Wrong: Using both props
<input
value={name}
defaultValue={user?.name || 'Guest'}
onChange={handleChange}
/>
// Correct: Initialize state with the conditional value
const [name, setName] = useState(user?.name || 'Guest');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);For controlled components, all conditional logic for initial values should be in your state initialization, not split between state and defaultValue.
If you're using a custom input wrapper or third-party component, check its props configuration:
// Your wrapper might be adding both props
function FormInput({ initialValue, currentValue, ...props }) {
return (
<input
defaultValue={initialValue} // Remove this
value={currentValue} // Keep this for controlled
{...props}
/>
);
}
// Fix: Choose one pattern
function FormInput({ value, onChange, ...props }) {
return (
<input
value={value}
onChange={onChange}
{...props}
/>
);
}Review the documentation for any form libraries you're using (React Hook Form, Formik, etc.) to ensure you're using their controlled/uncontrolled API correctly.
After making changes, test the following scenarios:
1. Initial value displays correctly when component mounts
2. Typing in the input updates as expected
3. Form submission captures the current value
4. Console no longer shows the warning
5. State updates propagate correctly to other dependent components
// Test that your controlled component works
function TestForm() {
const [value, setValue] = useState('initial');
console.log('Current value:', value); // Should update on every keystroke
return (
<div>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<p>You typed: {value}</p>
</div>
);
}Check the browser console to confirm the warning is gone, and manually test all form interactions.
Key Architectural Difference: The controlled vs. uncontrolled distinction reflects two fundamentally different philosophies about state management. Controlled components embrace React's "single source of truth" principle—the React state is the authoritative source for the input's value. Uncontrolled components follow the traditional HTML form pattern where the DOM itself is the source of truth, and React only sets initial values.
Performance Considerations: Uncontrolled components can be more performant for large forms because they don't trigger re-renders on every keystroke. However, this comes at the cost of reduced control and more complex validation. For most applications, the re-render overhead of controlled components is negligible, and modern React with concurrent features handles this efficiently.
Switching Between Patterns: Never change a component from controlled to uncontrolled (or vice versa) during its lifetime. React will warn with "A component is changing an uncontrolled input to be controlled" if you start with value={undefined} and later provide a defined value. Always initialize controlled inputs with a defined value, even if it's an empty string.
Framework Integration: Some form libraries (React Hook Form, Formik) offer hybrid approaches that combine the performance benefits of uncontrolled components with the API convenience of controlled components. They use refs internally but expose a controlled-like API. Check your library's documentation to understand which pattern it uses.
TypeScript Gotchas: TypeScript's type definitions for input props allow both value and defaultValue, so the type system won't catch this error. Consider creating wrapper components with stricter types that enforce either controlled or uncontrolled patterns:
type ControlledInputProps = {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
defaultValue?: never; // Explicitly disallow
};
type UncontrolledInputProps = {
defaultValue?: string;
value?: never; // Explicitly disallow
};
type InputProps = ControlledInputProps | UncontrolledInputProps;Resetting Forms: With uncontrolled components, you can't easily reset values programmatically since React doesn't control them. If you need to reset forms, controlled components are simpler. Alternatively, use the key prop to force remounting:
<input key={resetKey} defaultValue="initial" />
// Increment resetKey to reset the formdefaultValue Doesn't Re-render: A common misconception is that changing defaultValue will update the input. It only affects the initial render. If you need dynamic updates, you must use a controlled component with the value prop.
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