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;
}
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>
);
};
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>
);
};
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[];
}
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>
);
};
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>
);
};
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>
);
};
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!!
Top comments (0)