Skip to main content
ESLint Interlace
Plugin: react-featuresRules

checked-requires-onchange-or-readonly

checked-requires-onchange-or-readonly rule

Keywords: React, controlled input, checkbox, radio, onChange, readOnly, form handling, ESLint rule, LLM-optimized

Ensures controlled inputs with checked or value props have an onChange handler or readOnly attribute. This rule is part of eslint-plugin-react-features and provides LLM-optimized error messages.

Quick Summary

AspectDetails
SeverityWarning (React best practice)
Auto-Fix❌ No (requires handler implementation)
CategoryReact
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForReact form handling, controlled components

Rule Details

Why This Matters

IssueImpactSolution
🚫 UncontrolledUser can't interact with inputAdd onChange handler
⚠️ React WarningConsole warnings in developmentProperly handle controlled state
🐛 Silent FailuresForm doesn't work as expectedUse readOnly for display-only
📋 State SyncState and UI out of syncImplement proper handlers

Examples

❌ Incorrect

// Missing onChange - user can't toggle checkbox
<input type="checkbox" checked={isChecked} />

// Missing onChange - user can't type
<input type="text" value={name} />

// Missing handler on textarea
<textarea value={description} />

// Select without handler
<select value={selectedOption}>
  <option value="a">A</option>
</select>

✅ Correct

// With onChange handler
<input 
  type="checkbox" 
  checked={isChecked} 
  onChange={(e) => setIsChecked(e.target.checked)} 
/>

// With onChange for text input
<input 
  type="text" 
  value={name} 
  onChange={(e) => setName(e.target.value)} 
/>

// With readOnly for display-only
<input type="text" value={displayValue} readOnly />

// Properly controlled textarea
<textarea 
  value={description} 
  onChange={(e) => setDescription(e.target.value)} 
/>

// Using onInput as alternative
<input 
  type="text" 
  value={search} 
  onInput={(e) => setSearch(e.target.value)} 
/>

Configuration

{
  rules: {
    'react-features/checked-requires-onchange-or-readonly': 'error'
  }
}

When to Use readOnly

ScenarioUse readOnlyUse onChange
Display calculated values
Show server-provided data
User-editable forms
Toggle checkboxes

Further Reading

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Dynamic Variable References

Why: Static analysis cannot trace values stored in variables or passed through function parameters.

// ❌ NOT DETECTED - Prop from variable
const propValue = computedValue;
<Component prop={propValue} /> // Computation not analyzed

Mitigation: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs.

Cross-Module Data Flow

Why: ESLint rules analyze one file at a time. Values imported from other modules cannot be traced.

// ❌ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not tracked

Mitigation: Apply the same rule to imported modules. Use module boundaries and explicit exports.