Interface Segregation Principle in React

August 27, 2024 5 minute read

The SOLID principles were the first software design concepts I ever studied and to this day they remain the most influential piece of knowledge in my career. If it wasn’t for them maybe I never would’ve started to pay attention to the quality of my code and the structure of my projects.

Even though they’re best suited for object-oriented development, I always keep them in the back of my mind regardless of the environment and patterns I’m working with.

If there’s one SOLID principle that I’ve managed to apply everywhere it’s the one about interface segregation.

The Interface Segregation Principle

This principle is about the idea that we should avoid creating large interfaces that contain many methods or values. Instead, we should create smaller ones that are more specific to the needs of the functions or classes that use them.

If you’re up for a history lesson, as far as I know, the principle was conceived at Xerox where their software had a single Job class that was responsible for all the tasks you could perform. With time this class grew to a point where its maintainability was a problem.

interface Job {
    fax(): void;
    scan(): void;
    print(): void;
}

function print(job: Job) {
    // We're using only a single method from the interface
    // But we expect the entire interface to be implemented
    job.print();
}

The code became simpler when they split it into smaller classes and interfaces that only covered specific tasks.

interface PrintJob {
    print(): void;
}

function print(job: PrintJob) {
    // We only expect the interface to implement the print method
    job.print();
}

This is only meant as a simple example to illustrate the point. Working with smaller interfaces is not only easier to implement and maintain, but it’s also easier to test.

When I was learning about these principles, though, I found it hard to translate them to front-end development. The only interfaces I interacted with were the prop definitions. And they were always specific to the component, reusing large interfaces wasn’t really a problem.

But after a few years, I found out that the abstract principle of not depending on values you don’t need actually has a place in React.

A Component that Depends on Too Many Things

Imagine that we have the following component that expects a user object to be passed to it as a prop:

interface Props {
    user: User;
}
function UserGreeting({ user }: Props) {
    return <h1>Hey, {user.name}!</h1>;
}

Don’t focus on the issues of referential equality or re-rendering, they’re not the point right now.

Our component needs a user object, it’s a mandatory prop so we provide it. But after a closer look at the implementation of the component, we notice that it’s actually using only a single value - the name.

This is a violation of the interface segregation principle.

This last sentence sounded quite cryptic but don’t be too bothered. This component is just a little bit deceptive. It communicates to the developers using it that it needs an object even though it uses only a fraction of it. But in programming, as in life, it helps to be honest.

If we depend on the entire object we may make it more difficult for the component to be used. It would be better if we’re clear about the values we need.

interface Props {
    name: string;
}

function UserGreeting({ name }: Props) {
    return <h1>Hey, {name}!</h1>;
}

The proxy metric we can use to see if we’ve improved our design is to evaluate whether it’s easier to test with this API.

With its previous props, we’d have to mock the entire user object when we wanted to test the component. Any changes to the user object, like adding an extra field, had to be reflected in the component’s tests - even though they wouldn’t be needed.

But with this implementation, we only need to change the primitive values the component expects and it wouldn’t be affected by future changes to the object.

It’s definitely simpler.

Prop Drilling

Another common way that we violate this principle is through prop drilling. This is a common anti-pattern found in all front-end frameworks. It happens when we pass a value through multiple components that don’t need it so it can reach a specific one.

function Dashboard({ user }) {
    return (
        <section>
            <Header />
            ...
        </section>
    )
}

function Header({ user }) {
    return (
        <header>
            <Navigation user={user} />
        </header>
    )
}

function Navigation({ user }) {
    return (
        <nav>
            <UserGreeting name={user.name} />
            ...
        </nav>
    )
}

function UserGreeting({ name }) {
    return <h1>Hey, {name}!</h1>;
}

This is a problem because we’re once again being deceptive to our code’s readers. Looking at our component’s props they’d believe that they need the user object so they can render something from it, but in fact, they’re only passing it to their child components. They don’t actually use it.

In this case, we need to look for a different solution.

In React, this is most often done by using a context or using a state management library that will allow our component to read the value directly.

function UserGreeting() {
    const user = useUser();
    return <h1>Hey, {user.name}!</h1>;
}

But another option that we often forget is component composition.

function Dashboard({ user }) {
    return (
        <section>
            <Header>
                <Navigation>
                    <UserGreeting name={user.name} />
                </Navigation>
            </Header>
        </section>
    )
}

This way we’re not passing values to components that don’t need them.

Interface Segregation Made Simple

The beauty in this idea is in how simply it can be described - code shouldn’t depend on values and methods it doesn’t use. If you don’t need something don’t ask for it. It’s as simple as that.

The only interfaces in the examples above are the prop definitions. We’re not doing anything more complex than we usually do. What’s important is that we’re following the main principle, it’s not about an arbitrary implementation.

We don’t want our code to depend on things it doesn’t need and that’s what interface segregation is about.

This is an Excerpt…

This is an excerpt from my upcoming book “Full Stack Tao” which will focus on the most important software development principles and how they can be applied both in the front-end and back-end to solve some of the common problems that we have.

To stay up to date with the book, you can subscribe from the newsletter below this form.

Tao of React

Learn proven practices about React application architecture, component design, testing and performance.