In this article, we are going to look at the SOLID principles and how to apply them in the React.
For the sake of simplicity, I will not provide the full implementation of some components. So let’s begin.
Single Responsibility Principle
First is the Single responsibility principle. This principle tells us that module should have one and only one reason to change.
Let’s imagine we are trying to build an application which displays users in a table.
We have a component which has a user list in the state. We are fetching users from some HTTP endpoint and each user is editable. This component violates the Single Responsibility Principle because it has more than 1 reason to change.
I can see these reasons.
- Every time I want to change the header of application.
- Every time I want to add a new component to the application (e.g. footer).
- Every time I want to change the user fetching mechanism, for example, the address of endpoint or protocol.
- Every time I want to change the user list table (e.g. column styling, etc…)
Solution. After you identify your reasons to change try to eliminate them by creating a suitable abstraction (component, function, …) for each reason.
Let’s try to fix that problem. Lets go refactor the App component.
This is our new container component UserList. We solved problem 3 (change the user fetching mechanism) by creating props functions fetchUser and saveUser. So when we want to change the HTTP endpoint we go to the function (save/fetch)User and change that there.
The last problem 4 (change the user list table) was resolved by creating a simple presentation component UserTable which encapsulated the HTML and styling of the user table.
Open Closed Principle
This principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”.
If you look at the UserList component above you can notice that if we want to display users in a different format, we have to modify the UserList’s render method. This is a violation of this principle.
We can obey this principle by using Component Composition.
Look at the refactored UserList component below.
Lets look how would we display users in a list by using our new component.
Liskov Substitution Principle
“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
Or if you don’t mind more accurate description.
Look at the example below.
After that, we have created a simple function showUserRoles which accept user as a parameter and prints all of the user’s roles to the console.
But after we called the showUserRoles function with ordinaryUser and adminUser instances, it crashed.
And why? The AdminUser looks like the User. It definitely “quacks” like the User because it has the same methods. The problem was with the “batteries”. Because, when creating the admin user, we created an object of roles instead of an array.
We violated the Liskov Substitution Principle because the showUserRoles function should work correctly with the User and its derivatives!
Fix is simple, we will just create an array of roles instead of an object.
Interface Segregation Principle
This principle tells us that we should not depend on things we don’t need.
This principle applies especially on static types languages because your dependencies are explicitly defined by interfaces.
Let’s bring an example.
When you would write a test for this component in Typescript or Flow, you have to mock the whole user because otherwise, your compiler will fail.
But still, it is more descriptive this way.
Dependency Inversion Principle
This principle tells us that we should depend upon abstractions, not concretions.
Let’s look at the example.
A high-level module should not depend on low-level details, both should depend on abstraction.
The App should not know how to fetch users. In order to fix that problem, we have to inverse the dependencies between App component and fetch so the UML diagram will look like this.
And the implementation.
It gives us power because we can easily change the fetching method and the App component will not change a bit!
And testing is really simple, we can easily mock these fetching functions.
Invest your time to create a better code, your colleagues and your future self will thank you for that.