SOLID Principal with React: Interface Segregation Principle
Published: 2024-05-04 21:52:01

## Goal 🚀

- Understand Interface Segregation Principle and how to apply it in React.

## What is SOLID Principle?

SOLID is an acronym for five design principles intended to make software designs more understandable, flexible, and maintainable. These principles are a subset of many principles promoted by Robert C. Martin, also known as Uncle Bob. The SOLID principles are:

- **S** ... Single Responsibility Principle
- **O** ... Open/Closed Principle
- **L** ... Liskov Substitution Principle
- **I** ... Interface Segregation Principle
- **D** ... Dependency Inversion Principle

## What is Interface Segregation Principle? (Definition)

The Interface Segregation Principle states that a client should not be forced to implement an interface that it doesn't use.
This principle deals with the disadvantages of implementing big interfaces. When a class implements an interface,
it should not have to implement methods that it does not use. This principle is intended to prevent classes from becoming too large and to avoid the need to implement methods that are not used.

## How to apply Interface Segregation Principle in React? 🤔

Let's say you have a `User` Interface

```tsx
interface User {
  id: number;
  email: string;
  password: string;
  name: string;
  age: number;
  birthday: string;
}
```

And you have to implement a `UserCard` component that displays the user's name, email and birth date.

Here is an example of `UserCard` component, which doesn't follow Interface Segregation Principle.

```tsx
interface UserCardBadExampleProps {
  user: User;
}

const UserCard: FC<UserCardBadExampleProps> = ({ user }) => {
  return (
    <div className="p-4 border rounded shadow">
      <h2 className="text-xl">{user.name}</h2>
      <p className="text-sm text-gray-500">{user.email}</p>
      <p className="text-sm text-gray-500">{user.birthday}</p>
    </div>
  );
};
```

In this example, `UserCard` component is using the `User` interface,
but it doesn't need all the properties of the `User` interface.
It only needs `name`,`email`, and `birthDay` properties.

This can possibley cause a problem when the `User` interface is updated.
For instance, if by any chance the `birthDay` property get renamed to `birthDate`,
`UserCard` component will break because it is still using the old property name `birthDay`.

This particular problem might sound like less likely to happen, but we also can't deny that **`UserCard` component is unnecessarily dependent on the `User` interface.**

To follow Interface Segregation Principle, you can modify the props of the `UserCard` component like this:

```tsx
interface UserCardGoodExampleProps {
  name: string;
  email: string;
  birthday: string;
}

const UserCard: FC<UserCardGoodExampleProps> = ({ name, email, birthday }) => {
  return (
    <div className="p-4 border rounded shadow">
      <h2 className="text-xl">{name}</h2>
      <p className="text-sm text-gray-500">{email}</p>
      <p className="text-sm text-gray-500">{birthday}</p>
    </div>
  );
};
```

By doing this, you can prevent the `UserCard` component from implementing unnecessary properties,
and implement only the properties it needs. This makes the component less dependent on the `User` interface.

## Advanced Example

Let's say `User` interface has another property `posts` like this 👇

```tsx
interface Post {
  id: number;
  createdAt: string;
  updatedAt: string;
  title: string;
  content: string;
  user: User;
}

interface User {
  id: number;
  email: string;
  password: string;
  name: string;
  age: number;
  birthday: string;
  posts: Post[];
}
```

And you have to extend the `UserCard` component to display the user's post titles, created dates, updated dates.

Considering the Interface Segregation Principle, we would modify the `UserCard` component like this:

```tsx
interface UserCardGoodExampleProps {
  name: string;
  email: string;
  birthday: string;
  posts: Post[];
}

const UserCard: FC<UserCardGoodExampleProps> = ({
  name,
  email,
  birthday,
  posts,
}) => {
  return (
    <div className="p-4 border rounded shadow">
      <h2 className="text-xl">{name}</h2>
      <p className="text-sm text-gray-500">{email}</p>
      <p className="text-sm text-gray-500">{birthday}</p>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.createdAt}</p>
            <p>{post.updatedAt}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};
```

My concern in this example is that the `UserCard` component is still dependent on the `Post` Interface.

To follow Interface Segregation Principle more strictly, we can modify the `UserCard` component like this:

```tsx
interface UserCardGoodExampleProps {
  name: string;
  email: string;
  birthday: string;
  postTitles: string[];
  postCreatedDates: string[];
  postUpdatedDates: string[];
}

const UserCard: FC<UserCardGoodExampleProps> = ({
  name,
  email,
  birthday,
  postTitles,
  postCreatedDates,
  postUpdatedDates,
}) => {
  return (
    <div className="p-4 border rounded shadow">
      <h2 className="text-xl">{name}</h2>
      <p className="text-sm text-gray-500">{email}</p>
      <p className="text-sm text-gray-500">{birthday}</p>
      <ul>
        {postTitles.map((title, index) => (
          <li key={index}>
            <h3>{title}</h3>
            <p>{postCreatedDates[index]}</p>
            <p>{postUpdatedDates[index]}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};
```

Or, we could divide the `UserCard` component into two components, `UserCard` and `PostList`.

```tsx
interface UserCardGoodExampleProps {
  name: string;
  email: string;
  birthday: string;
  children: ReactNode;
}

const UserCard: FC<UserCardGoodExampleProps> = ({
  name,
  email,
  birthday,
  children,
}) => {
  return (
    <div className="p-4 border rounded shadow">
      <h2 className="text-xl">{name}</h2>
      <p className="text-sm text-gray-500">{email}</p>
      <p className="text-sm text-gray-500">{birthday}</p>
      {children}
    </div>
  );
};

interface PostProps {
  title: string;
  createdAt: string;
  updatedAt: string;
}

const Post: FC<PostProps> = ({ title, createdAt, updatedAt }) => {
  return (
    <li>
      <h3>{title}</h3>
      <p>{createdAt}</p>
      <p>{updatedAt}</p>
    </li>
  );
};

const SamplePage = () => {
  const user: User = await fetchUser();

  return (
    <UserCard name={user.name} email={user.email} birthday={user.birthday}>
      <ul>
        {user.posts.map((post) => (
          <Post
            key={post.id}
            title={post.title}
            createdAt={post.createdAt}
            updatedAt={post.updatedAt}
          />
        ))}
      </ul>
    </UserCard>
  );
};
```

I'm actually not sure if we should follow ISP this much stricltly tho 😅

Please let me know if you have any thoughts on this!!