A fundamental React anti-pattern is attempting to modify props directly within a child component. This violates React’s unidirectional data flow principle and can lead to unpredictable behavior.
The Problem
// ❌ Wrong - directly mutating props
function ChildComponent({ user }) {
user.name = "Modified"; // Direct mutation
return <div>{user.name}</div>;
}
Why this is problematic:
- Props should be treated as immutable in React
- Parent component won’t be aware of the change
- Can cause inconsistencies between parent and child state
- Makes data flow harder to trace and debug
Correct Solutions
1. Lift State Up (Recommended)
Move state management to the parent component and pass down both the value and updater function:
// ✅ Parent component manages state
function ParentComponent() {
const [user, setUser] = useState({ name: "Original" });
return (
<ChildComponent
user={user}
onUpdateUser={(newName) => setUser({...user, name: newName})}
/>
);
}
function ChildComponent({ user, onUpdateUser }) {
return (
<div>
<div>{user.name}</div>
<button onClick={() => onUpdateUser("Modified")}>
Change Name
</button>
</div>
);
}
2. Use Local State Derived from Props
For cases where you need to modify values locally:
function ChildComponent({ initialUser }) {
const [localUser, setLocalUser] = useState(initialUser);
const handleUpdate = () => {
setLocalUser({...localUser, name: "Modified"});
};
return (
<div>
<div>{localUser.name}</div>
<button onClick={handleUpdate}>Change Locally</button>
</div>
);
}
3. Use Controlled Components Pattern
When dealing with form inputs:
function InputComponent({ value, onChange }) {
return (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
}
Why Props Should Be Immutable
- Single Source of Truth: Data flows from parent to child
- Predictable Rendering: Components render based on props/state
- Performance Optimizations: React relies on prop references for re-renders
- Debugging Simplicity: Clear data flow makes issues easier to track
Common Anti-Patterns to Avoid
- Direct object mutation:
// ❌ Wrong
props.user.name = "New Name";
- Array mutations:
// ❌ Wrong
props.items.push(newItem);
- Reassigning props:
// ❌ Wrong (though this just creates a local variable)
props = { ...props, modified: true };
- Two-way binding attempts:
// ❌ Wrong pattern
<ChildComponent user={user} onChange={handleChange} />
// Then in child:
<input value={props.user.name}
onChange={(e) => props.user.name = e.target.value} />
Best Practices
- Treat all props as read-only in child components
- Communicate changes via callback functions
- Use local state for UI-specific modifications
- Clone objects/arrays if you need to modify them locally
- Consider Context API for deep prop drilling scenarios