logoESLint React
Rules

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-props

Full Name in @eslint-react/eslint-plugin@rc

@eslint-react/unstable-rules-of-props

Features

🧪

Presets

Rule Details

This rule covers two concerns:

  1. Duplicate props on the same element.
  2. 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 propUncontrolled prop
valuedefaultValue
checkeddefaultChecked
opendefaultOpen
pagedefaultPage
selectedItemdefaultSelectedItem
any foodefaultFoo

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} />;

Resources

Further Reading

On this page