Forem

Cover image for Interface Segregation Principle (ISP) in React Development
Chhakuli Zingare for CreoWis

Posted on • Originally published at creowis.com

Interface Segregation Principle (ISP) in React Development

Welcome back to our SOLID principles in the React series! So far, we’ve covered:

S: Single Responsibility Principle (SRP): Keep components focused.

O: Open/Closed Principle (OCP): Make extensions easy, not modifications.

L: Liskov Substitution Principle (LSP): Keep components interchangeable without breaking behavior.

Today, we’re diving into the Interface Segregation Principle (ISP)—a principle that helps keep our React components and interfaces lean, modular, and easier to maintain.

If SOLID principles were a gym for your code, ISP would be the exercise that trims down bloated, unfocused interfaces. Let’s break it down and then apply it to real-world React examples.

What is the Interface Segregation Principle (ISP)?

ISP is one of the five SOLID principles from object-oriented programming (OOP), but it’s just as relevant in React.

In simple terms:

“A class (or component) should not be forced to depend on interfaces it does not use.”

In React terms:

  • A component should only receive the props it actually needs.

  • Interfaces (types) should be small and specific, not overloaded with unrelated properties.

  • Hooks should return only the data relevant to their purpose.

If an interface has too many properties, components will be bloated, harder to reuse, and difficult to maintain.

In simple terms, ISP states that an interface should only have the methods and properties relevant to a specific use case. In React, this means breaking down component props and interfaces into smaller, focused ones. This improves maintainability, reusability, and readability.

Why Should React Developers Care?

Before we dive into code, let's talk about why this matters in real-world React development:

  1. Easier Testing: When components have fewer responsibilities, they're much easier to test. You don't have to mock a bunch of unused props just to test one feature.

  2. Better Reusability: Smaller, focused components are like LEGO blocks - you can mix and match them in different ways to build different features.

  3. Simpler Maintenance: When something breaks, you know exactly where to look. Plus, changing one component is less likely to cause unexpected issues elsewhere.

  4. Improved Performance: React can better optimize renders when components only depend on the props they actually use.

Why Does ISP Matter in React?

Violating ISP often leads to these common issues:

  • Bloated Components: Components receive too many props, making them complex and hard to manage.

  • Unnecessary Re-renders: A component re-renders even when only an unrelated prop changes.

  • Poor Reusability: A component tied to a massive interface becomes difficult to reuse elsewhere.

The Problem: A Monolithic Component

Let's look at a common mistake: creating a component that tries to do too much.

Here's an example of a user profile card that violates ISP:

interface UserProfileProps {
  // Basic user info
  userId: string;
  name: string;
  email: string;
  avatar: string;

  // Social media links
  twitter?: string;
  linkedin?: string;
  github?: string;

  // Activity stats
  postCount: number;
  commentCount: number;
  likeCount: number;

  // Admin features
  isAdmin: boolean;
  canDelete: boolean;
  canEdit: boolean;

  // Event handlers
  onEdit: () => void;
  onDelete: () => void;
  onFollow: () => void;
  onMessage: () => void;
}

const UserProfile: React.FC<UserProfileProps> = ({
  userId,
  name,
  email,
  avatar,
  twitter,
  linkedin,
  github,
  postCount,
  commentCount,
  likeCount,
  isAdmin,
  canDelete,
  canEdit,
  onEdit,
  onDelete,
  onFollow,
  onMessage
}) => {
  return (
    <div className="user-profile">
      <div className="user-basic-info">
        <img src={avatar} alt={name} />
        <h2>{name}</h2>
        <p>{email}</p>
      </div>

      <div className="social-links">
        {twitter && <a href={twitter}>Twitter</a>}
        {linkedin && <a href={linkedin}>LinkedIn</a>}
        {github && <a href={github}>GitHub</a>}
      </div>

      <div className="activity-stats">
        <span>Posts: {postCount}</span>
        <span>Comments: {commentCount}</span>
        <span>Likes: {likeCount}</span>
      </div>

      <div className="actions">
        <button onClick={onFollow}>Follow</button>
        <button onClick={onMessage}>Message</button>
        {isAdmin && canEdit && <button onClick={onEdit}>Edit</button>}
        {isAdmin && canDelete && <button onClick={onDelete}>Delete</button>}
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

See the problem? This component is doing way too much:

  • Handling basic user information

  • Managing social media links

  • Displaying activity statistics

  • Dealing with admin functionality

  • Managing various user interactions

Applying ISP: Breaking It Down

Let's refactor this component using ISP. We'll break it into smaller, focused interfaces and components:

// Basic user information interface
interface UserBasicInfoProps {
  name: string;
  email: string;
  avatar: string;
}

const UserBasicInfo: React.FC<UserBasicInfoProps> = ({ name, email, avatar }) => (
  <div className="user-basic-info">
    <img src={avatar} alt={name} />
    <h2>{name}</h2>
    <p>{email}</p>
  </div>
);

// Social links interface
interface SocialLinksProps {
  twitter?: string;
  linkedin?: string;
  github?: string;
}

const SocialLinks: React.FC<SocialLinksProps> = ({ twitter, linkedin, github }) => (
  <div className="social-links">
    {twitter && <a href={twitter}>Twitter</a>}
    {linkedin && <a href={linkedin}>LinkedIn</a>}
    {github && <a href={github}>GitHub</a>}
  </div>
);

// Activity stats interface
interface ActivityStatsProps {
  postCount: number;
  commentCount: number;
  likeCount: number;
}

const ActivityStats: React.FC<ActivityStatsProps> = ({ 
  postCount, 
  commentCount, 
  likeCount 
}) => (
  <div className="activity-stats">
    <span>Posts: {postCount}</span>
    <span>Comments: {commentCount}</span>
    <span>Likes: {likeCount}</span>
  </div>
);

// Admin actions interface
interface AdminActionsProps {
  isAdmin: boolean;
  canEdit: boolean;
  canDelete: boolean;
  onEdit: () => void;
  onDelete: () => void;
}

const AdminActions: React.FC<AdminActionsProps> = ({
  isAdmin,
  canEdit,
  canDelete,
  onEdit,
  onDelete
}) => {
  if (!isAdmin) return null;

  return (
    <div className="admin-actions">
      {canEdit && <button onClick={onEdit}>Edit</button>}
      {canDelete && <button onClick={onDelete}>Delete</button>}
    </div>
  );
};

// User interaction interface
interface UserInteractionProps {
  onFollow: () => void;
  onMessage: () => void;
}

const UserInteractions: React.FC<UserInteractionProps> = ({
  onFollow,
  onMessage
}) => (
  <div className="user-interactions">
    <button onClick={onFollow}>Follow</button>
    <button onClick={onMessage}>Message</button>
  </div>
);

// Main UserProfile component now composing smaller components
interface UserProfileProps {
  user: UserBasicInfoProps;
  social: SocialLinksProps;
  stats: ActivityStatsProps;
  admin: AdminActionsProps;
  interactions: UserInteractionProps;
}

const UserProfile: React.FC<UserProfileProps> = ({
  user,
  social,
  stats,
  admin,
  interactions
}) => {
  return (
    <div className="user-profile">
      <UserBasicInfo {...user} />
      <SocialLinks {...social} />
      <ActivityStats {...stats} />
      <AdminActions {...admin} />
      <UserInteractions {...interactions} />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

What Changed and Why It's Better

Let's break down the improvements:

Focused Components: Each component now has a single responsibility:

  • UserBasicInfo handles only basic user data

  • SocialLinks manages only social media links

  • ActivityStats displays only user statistics

  • AdminActions handles admin-specific functionality

  • UserInteractions manages user engagement actions

Flexible Composition: Now you can easily:

  • Use components independently where needed

  • Test each component in isolation

  • Modify one aspect without touching others

Better Props Management: Each component receives only the props it needs, making it:

  • Easier to understand what each component requires

  • Simpler to maintain type safety

  • More efficient with React's rendering

Real-World Example: A Data Table Component

Here's a practical example of how ISP can help with a common React pattern data table:

// Instead of one massive table interface
interface DataTableProps<T> {
  data: T[];
  columns: Column[];
  sortable?: boolean;
  filterable?: boolean;
  pagination?: boolean;
  pageSize?: number;
  onSort?: (column: string) => void;
  onFilter?: (filters: Filter[]) => void;
  onPageChange?: (page: number) => void;
  loading?: boolean;
  error?: Error;
  selection?: boolean;
  onSelect?: (items: T[]) => void;
  // ... many more props
}

// Break it down using ISP
interface TableDataProps<T> {
  data: T[];
  columns: Column[];
}

interface SortableProps {
  sortable?: boolean;
  onSort?: (column: string) => void;
}

interface FilterableProps {
  filterable?: boolean;
  onFilter?: (filters: Filter[]) => void;
}

interface PaginationProps {
  pagination?: boolean;
  pageSize?: number;
  onPageChange?: (page: number) => void;
}

interface TableStateProps {
  loading?: boolean;
  error?: Error;
}

interface SelectionProps<T> {
  selection?: boolean;
  onSelect?: (items: T[]) => void;
}

// Compose them using higher-order components or hooks
const DataTable = <T extends unknown>(props: TableDataProps<T>) => {
  // Base table implementation
};

const withSorting = <T extends unknown>(
  WrappedComponent: React.ComponentType<TableDataProps<T>>
) => {
  return (props: TableDataProps<T> & SortableProps) => {
    // Add sorting functionality
    return <WrappedComponent {...props} />;
  };
};

const withFiltering = <T extends unknown>(
  WrappedComponent: React.ComponentType<TableDataProps<T>>
) => {
  return (props: TableDataProps<T> & FilterableProps) => {
    // Add filtering functionality
    return <WrappedComponent {...props} />;
  };
};

// Usage
const SortableTable = withSorting(DataTable);
const FilterableTable = withFiltering(DataTable);
const FullFeaturedTable = withFiltering(withSorting(DataTable));
Enter fullscreen mode Exit fullscreen mode

Common Mistakes to Avoid

While applying ISP, watch out for these pitfalls:

1. Over-segregation

// Too granular!
interface NameProps { name: string; }
interface EmailProps { email: string; }
interface AvatarProps { avatar: string; }

// Better balance
interface UserBasicInfoProps {
  name: string;
  email: string;
  avatar: string;
}
Enter fullscreen mode Exit fullscreen mode

2. Prop Drilling

// Passing admin props through multiple layers
const App = () => (
  <UserProfile 
    adminProps={adminProps} 
    userProps={userProps} 
  />
);

// Better approach: Use Context or composition
const AdminContext = React.createContext<AdminProps | null>(null);

const App = () => (
  <AdminContext.Provider value={adminProps}>
    <UserProfile userProps={userProps} />
  </AdminContext.Provider>
);
Enter fullscreen mode Exit fullscreen mode

3. Premature Abstraction

// Creating interfaces for everything
interface ButtonProps {
  onClick: () => void;
  children: React.ReactNode;
}

// Use built-in types when they suffice
const Button: React.FC<React.ButtonHTMLAttributes<HTMLButtonElement>> = (props) => (
  <button {...props} />
);
Enter fullscreen mode Exit fullscreen mode

When to Apply ISP in Real Projects

ISP isn't always necessary. Here's when you should consider it:

Good Times to Apply ISP:

  1. When components have multiple distinct responsibilities

  2. When different parts of a component change for different reasons

  3. When you find yourself passing many unused props through components

  4. When testing becomes complicated due to component complexity

Maybe Skip ISP When:

  1. Working with simple, focused components

  2. The overhead of separation doesn't provide clear benefits

  3. Components are unlikely to change or be reused

Key Takeaways

  1. Start Simple: Begin with cohesive components and split them only when needed.

  2. Follow Dependencies: Let natural dependencies guide your interface boundaries.

  3. Think Reusability: Design interfaces with reuse in mind, but don't over-abstract.

  4. Consider Testing: If testing becomes complex, it might be time to split interfaces.

  5. Balance Trade-offs: Sometimes a slightly larger interface is better than extreme segregation.

Final Thoughts: Keep Your Interfaces Lean & Focused

The Interface Segregation Principle (ISP) is all about clarity, performance, and maintainability in React.

  • Avoid bloated interfaces that force components to accept unnecessary props.

  • Split interfaces wisely to make components easier to reuse.

  • Improve performance by preventing unnecessary re-renders.

This is not just about following rules blindly; it's about writing components that are easier to understand, test, and maintain. By breaking down large interfaces into focused, purpose-specific ones, we create more flexible and maintainable React applications.

Remember: The goal isn't to have the smallest possible interfaces, but to have interfaces that serve a clear, single purpose. Start with reasonable component sizes and refactor when you see clear benefits from separation.

Keep practicing these patterns, and you'll develop an intuition for when and how to apply ISP in your React projects.

This wraps up our “I” from our SOLID principles in the React series! If you’ve learned something new, share this post with your fellow React developers.

Next, we’ll tackle the Dependency Inversion Principle (DIP)

Until then, keep your React code SOLID, and let’s build something amazing together.

Stay tuned, and happy coding!


We at CreoWis believe in sharing knowledge publicly to help the developer community grow. Let’s collaborate, ideate, and craft passion to deliver awe-inspiring product experiences to the world.

Let's connect:

This article is crafted by Chhakuli Zingare, a passionate developer at CreoWis. You can reach out to her on X/Twitter, LinkedIn, and follow her work on the GitHub.

Top comments (1)

Collapse
 
wizard798 profile image
Wizard

Just amazing, without knowing this principle a always used this principle ( most time properly ) in my projects to mainly decrease complexity, as having too many props are hard to handle and causes many unnecessary re renders, so I always break them into smaller parts and then recombine them in final to reduce complexity and following react main principle that function should only have 1 responsibility, but with this guide I'll be using it more carefully to make React app more efficient