unstable-rules-of-props
Enforces the Rules of Props.
This rule is currently in rc and only available in v3.0.0 rc releases.
This rule is experimental and may change in the future or be removed. It is not recommended for use in production code at this time.
Full Name in eslint-plugin-react-x@rc
react-x/unstable-rules-of-propsFull Name in @eslint-react/eslint-plugin@rc
@eslint-react/unstable-rules-of-propsFeatures
🧪
Presets
Rule Details
This rule covers two concerns:
- Duplicate props on the same element.
- Using conflicting prop combinations (controlled and uncontrolled props together).
Common Violations
Specifying the Same Prop More Than Once
When the same prop appears multiple times on a JSX element, only the last occurrence takes effect. The earlier values are silently discarded, which is almost always a mistake — typically a copy-paste error or a merge conflict leftover.
Spread props are ignored because it may be intentional to override a prop from the spread with an explicit prop.
Invalid
// ❌ 'id' is specified twice — only the last value takes effect.
<div id="a" id="b" />;// ❌ Duplicate props on a custom component.
<MyComponent foo="a" foo="b" />;// ❌ Duplicate boolean props.
<input disabled disabled />;// ❌ Duplicate event handlers.
<div onClick={handleA} onClick={handleB} />;Valid
// ✅ All props are unique.
<div id="a" className="b" />;// ✅ Spread attributes are ignored.
<div id="a" {...props} />;// ✅ Same prop on different elements is fine.
<div>
<span id="a" />
<span id="b" />
</div>;Using Controlled and Uncontrolled Props Together
Mixing both modes on the same element is a mistake. React will silently ignore the default* prop and may emit a console warning. The element behaves as controlled, which causes confusing bugs when the developer expects the initial value to be respected.
Any prop pair of the form foo and defaultFoo must not appear together on the same element. This includes built-in React pairs as well as custom component props that follow the same convention:
| Controlled prop | Uncontrolled prop |
|---|---|
value | defaultValue |
checked | defaultChecked |
open | defaultOpen |
page | defaultPage |
selectedItem | defaultSelectedItem |
any foo | defaultFoo |
Invalid
// ❌ 'defaultValue' is ignored because 'value' makes the input controlled.
<input value={name} defaultValue="World" />;// ❌ 'defaultChecked' is ignored because 'checked' makes the checkbox controlled.
<input type="checkbox" checked={isChecked} defaultChecked />;// ❌ Order does not matter — the pair is still invalid.
<textarea defaultValue="fallback" value={content} />;// ❌ Custom components that follow the same pattern are also flagged.
<MyInput value={val} defaultValue="fallback" />;// ❌ Any foo + defaultFoo pair on a custom component is flagged.
<Dropdown open={isOpen} defaultOpen={false} />;// ❌ Multi-word camelCase pairs are also covered.
<Select selectedItem={item} defaultSelectedItem={null} />;Valid
// ✅ Controlled input — value is driven by React state.
<input value={name} onChange={(e) => setName(e.target.value)} />;// ✅ Uncontrolled input — host element manages the value; initial value is provided.
<input defaultValue="World" />;// ✅ Controlled checkbox.
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>;// ✅ Uncontrolled checkbox with an initial checked state.
<input type="checkbox" defaultChecked />;// ✅ Only the uncontrolled prop is present — fine.
<Dropdown defaultOpen={false} />;// ✅ Only the controlled prop is present — fine.
<Dropdown open={isOpen} onOpenChange={setIsOpen} />;