DEV Community

Tanvir Azad
Tanvir Azad

Posted on

Why Passing Entire Objects as Props in React is a Bad Practice

When building React applications, you'll frequently need to pass data from parent components to children. Many developers, especially those new to React, often take the seemingly convenient approach of passing entire objects as props. However, this practice can lead to significant problems with performance, maintainability, and code quality.

Let's examine why passing entire objects is problematic through the lens of React's reconciliation process and SOLID principles, using a practical scenario.


The Scenario: A User Profile Management System

Imagine we're building a user profile dashboard with various components that display and edit different aspects of a user's information:

  • Profile header showing name and avatar
  • Contact information section
  • Account settings section
  • Subscription details

The Problematic Approach: Passing the Entire User Object

// Parent component
function UserDashboard() {
  const [user, setUser] = useState({
    id: "user_123",
    name: "Jane Smith",
    email: "[email protected]",
    avatar: "/avatars/jane.png",
    role: "Administrator",
    department: "Engineering",
    lastLogin: "2025-05-10T10:30:00",
    preferences: {
      theme: "dark",
      notifications: true,
      language: "en-US"
    },
    subscription: {
      plan: "premium",
      renewalDate: "2025-06-15",
      paymentMethod: "credit_card"
    }
  });

  const updateUser = (updatedFields) => {
    setUser({...user, ...updatedFields});
  };

  return (
    <div className="dashboard">
      <ProfileHeader user={user} />
      <ContactInfo user={user} updateUser={updateUser} />
      <AccountSettings user={user} updateUser={updateUser} />
      <SubscriptionDetails user={user} updateUser={updateUser} />
    </div>
  );
}

// One of the child components
function ProfileHeader({ user }) {
  // Only uses name and avatar
  return (
    <header>
      <img src={user.avatar} alt={user.name} />
      <h1>{user.name}</h1>
    </header>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Problems with This Approach

1. React Reconciliation Inefficiency

React's reconciliation process determines when components need to re-render. When passing an entire object as props, any change to that object (even to properties the component doesn't use) creates a new object reference, triggering unnecessary re-renders.

Reconciliation Comparison Table

Scenario Passing Entire Object Passing Individual Properties
User changes name All components re-render (new object reference) Only components using name re-render
User changes theme preference All components re-render (new object reference) Only components using preferences re-render
Component shallow comparison check prevProps.user !== nextProps.user (always true with new reference) Only changed properties fail equality check
Memoization effectiveness Poor (entire object changes frequently) Good (individual properties change only when needed)
Re-render trigger granularity Coarse (any field change triggers all components) Fine (only affected components re-render)
Performance impact on large apps Significant render cascades Minimal, targeted re-renders

In our example:

function ContactInfo({ user, updateUser }) {
  // Only uses email and department
  return (
    <div>
      <input 
        value={user.email}
        onChange={(e) => updateUser({ email: e.target.value })} 
      />
      <select 
        value={user.department}
        onChange={(e) => updateUser({ department: e.target.value })}
      >
        {/* Department options */}
      </select>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

If a user changes their theme preference:

  1. The updateUser function is called with { preferences: {...user.preferences, theme: "light" }}
  2. The entire user object gets a new reference
  3. All four components re-render, even though only AccountSettings actually needs to reflect this change

This creates a needless performance bottleneck, especially as the application grows.

2. Violations of SOLID Principles

Single Responsibility Principle

Each component should have one reason to change. By passing the entire user object, we create multiple reasons for a component to change:

  • Changes to user data structure
  • Changes to component rendering logic

ProfileHeader doesn't need to know about subscription details or preferences, yet changes to those will affect it.

Interface Segregation Principle

Components should only know about the data they need. Our ProfileHeader component only needs name and avatar, but it receives the entire user object with all its properties. This violates the Interface Segregation Principle, which states that "clients should not be forced to depend upon interfaces they do not use."

Open/Closed Principle

Components that receive entire objects are less resilient to change. If we modify the user object structure (like adding a new field or renaming one), every component receiving that object is potentially affected, even if they don't use the changed field.

3. Practical Problems in Development

3.1 Reduced Type Safety

With TypeScript:

// With entire object
function ProfileHeader({ user }: { user: User }) {
  // No clarity about which properties are actually used
}

// With individual props
function ProfileHeader({ name, avatar }: { name: string, avatar: string }) {
  // Type system explicitly shows required properties
}
Enter fullscreen mode Exit fullscreen mode

3.2 Testing Complications

When testing components that receive entire objects, you need to provide complete mock objects, even for properties your component doesn't use.

3.3 Debugging Challenges

When debugging render issues, it's much harder to trace which property change triggered a re-render when passing entire objects.


The Better Approach: Passing Only Required Properties

Let's refactor our example:

function UserDashboard() {
  const [user, setUser] = useState({
    // Same user object as before
  });

  const updateUser = (updatedFields) => {
    setUser({...user, ...updatedFields});
  };

  return (
    <div className="dashboard">
      <ProfileHeader 
        name={user.name} 
        avatar={user.avatar} 
      />
      <ContactInfo 
        email={user.email} 
        department={user.department} 
        onEmailChange={(email) => updateUser({ email })}
        onDepartmentChange={(department) => updateUser({ department })}
      />
      <AccountSettings 
        preferences={user.preferences}
        onPreferencesChange={(preferences) => 
          updateUser({ preferences: {...user.preferences, ...preferences} })}
      />
      <SubscriptionDetails 
        subscription={user.subscription}
        onSubscriptionChange={(subscription) => 
          updateUser({ subscription: {...user.subscription, ...subscription} })}
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Benefits of the Improved Approach

1. Efficient Reconciliation

Now, when a user changes their theme:

  1. Only the preferences object gets a new reference
  2. Only the AccountSettings component re-renders
  3. Other components remain untouched, as React can see their props haven't changed

2. SOLID Principles Adherence

  • Single Responsibility: Each component only depends on the data it needs
  • Interface Segregation: Components receive exactly the interface they require
  • Open/Closed: Changes to one part of the user data don't affect components that use other parts

3. Developer Experience Improvements

  • Clear, self-documenting API for each component
  • Easier testing with fewer mock objects needed
  • Explicit dependencies make refactoring safer

Conclusion

While passing entire objects as props might seem convenient, it creates unnecessary performance overhead and violates key software design principles. By passing only the properties that components actually need:

  1. Your app will re-render more efficiently
  2. Your component interfaces will be clearer and more maintainable
  3. Your codebase will be more resilient to changes

This practice becomes increasingly important as your application grows in complexity. Start with good habits now by passing only the props your components actually need, and you'll thank yourself later when debugging or optimizing your React application.

Remember: "Explicit is better than implicit" - even if it means writing a few more lines of prop definitions, the clarity and performance benefits make it worthwhile.

Top comments (0)