SOLID Principal with React: Interface Segregation Principle

kanta1207

kanta1207

Posted on May 4, 2024

SOLID Principal with React: Interface Segregation Principle

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

interface User {
  id: number;
  email: string;
  password: string;
  name: string;
  age: number;
  birthday: string;
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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 👇

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[];
}
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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!!

💖 💪 🙅 🚩
kanta1207
kanta1207

Posted on May 4, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related