Properly Handling Child Component Props in React
A common React anti-pattern is trying to modify a child component’s props directly or using them incorrectly. Here’s how to properly work with child component props:
The Core Principle: Props Are Read-Only
Remember: Props flow down from parent to child and should never be modified by the child component. This is a fundamental React principle.
Common Mistakes and Solutions
1. Trying to Modify Props Directly (Anti-pattern)
// ❌ Wrong - never modify props directly
function ChildComponent({ value }) {
value = value + 1; // Mutating props!
return <div>{value}</div>;
}
Solution: Use state derived from props if you need to modify values:
// ✅ Correct - use state derived from props
function ChildComponent({ initialValue }) {
const [value, setValue] = useState(initialValue);
const increment = () => {
setValue(prev => prev + 1);
};
return (
<div>
{value}
<button onClick={increment}>Increment</button>
</div>
);
}
2. Not Using Key Prop When Rendering Lists
// ❌ Problematic - missing key prop
{items.map(item => <ChildComponent value={item.value} />)}
Solution: Always include a unique key:
// ✅ Correct - with key prop
{items.map(item => (
<ChildComponent key={item.id} value={item.value} />
))}
3. Passing Too Many Props (Prop Drilling)
// ❌ Problematic - excessive prop drilling
<Parent>
<Child>
<GrandChild>
<GreatGrandChild someProp={value} />
</GrandChild>
</Child>
</Parent>
Solutions:
- Use React Context
- Component composition
- State management library (Redux, MobX, etc.)
4. Passing Objects Without Memoization
// ❌ Problematic - new object reference on each render
<ChildComponent config={{ width: 100, height: 200 }} />
Solutions:
// ✅ Solution 1 - Memoize with useMemo
const config = useMemo(() => ({ width: 100, height: 200 }), []);
// ✅ Solution 2 - Define outside component
const CONFIG = { width: 100, height: 200 };
function Parent() {
return <ChildComponent config={CONFIG} />;
}
Best Practices for Child Component Props
- Treat props as immutable – Never modify them directly
- Use TypeScript or PropTypes – Catch prop issues early
- Destructure props for cleaner code:
function ChildComponent({ title, description, onClick }) {
// component logic
}
- Default props for optional values:
ChildComponent.defaultProps = {
disabled: false,
size: 'medium'
};
Or with modern JavaScript:
function ChildComponent({ disabled = false, size = 'medium' }) {
// component logic
}
Advanced Patterns
1. Controlled Components Pattern
function Parent() {
const [value, setValue] = useState('');
return (
<ChildInput
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
function ChildInput({ value, onChange }) {
return <input value={value} onChange={onChange} />;
}
2. Children as a Function (Render Props)
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return <div onMouseMove={handleMouseMove}>{render(position)}</div>;
}
// Usage:
<MouseTracker render={({ x, y }) => (
<div>Mouse position: {x}, {y}</div>
)} />
Remember that proper prop handling leads to more maintainable, predictable components that are easier to debug and test.