Mutating props inside a child component

Loading

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:

  1. Props should be treated as immutable in React
  2. Parent component won’t be aware of the change
  3. Can cause inconsistencies between parent and child state
  4. 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

  1. Single Source of Truth: Data flows from parent to child
  2. Predictable Rendering: Components render based on props/state
  3. Performance Optimizations: React relies on prop references for re-renders
  4. Debugging Simplicity: Clear data flow makes issues easier to track

Common Anti-Patterns to Avoid

  1. Direct object mutation:
   // ❌ Wrong
   props.user.name = "New Name";
  1. Array mutations:
   // ❌ Wrong
   props.items.push(newItem);
  1. Reassigning props:
   // ❌ Wrong (though this just creates a local variable)
   props = { ...props, modified: true };
  1. 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

  1. Treat all props as read-only in child components
  2. Communicate changes via callback functions
  3. Use local state for UI-specific modifications
  4. Clone objects/arrays if you need to modify them locally
  5. Consider Context API for deep prop drilling scenarios

Leave a Reply

Your email address will not be published. Required fields are marked *