This article is a part of an ongoing series where we deep dive into SOLID principles and how we can leverage it to better architect our large-scale front-end applications. We discuss how SOLID principles fit in to the Functional Programming Paradigm of JavaScript.
If you are coming from an object oriented programming world (especially Java or C#) you have already heard and used SOLID principles. SOLID is an acronym for 5 important design principles when doing OOP (Object Oriented Programming).
SOLID stands for
S – Single Responsibility Principle (SRP in short)
O – Open Close Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle and
D – Dependency Inversion Principle
In this article we are going to discuss the S of SOLID. Single Responsibility Principle (SRP) and how it applies to React as well as Functional world of JavaScript. I have further broken down the topic as shown below.
- Importance of writing single responsibility components
a. Case study: when to break down components - Higher Order Component (HOC) patterns to separate responsibility and concerns in components
a. Props Proxy techniques in HOC to separate concerns
b. Render Hijacking techniques to compose components - Curry functions and how to use curried functions and partial application for separation of concerns further
- Leveraging React Context for SRP
React is Component based. We can build encapsulated components that manage their own state, then compose them to make complex UIs.
Component-based development is productive, easy to manage and maintain. A very complex system can be built relatively easily from specialized and easy to manage pieces. However, if the components are not well designed we cannot reuse and compose them efficiently. Bulky tightly coupled components with many responsibilities only increases technical debt. As our application grows it becomes harder to add new functionality or update existing ones.
Let’s take a look at the following UsersComponent
import React from "react"; import ReactDOM from "react-dom"; import axios from "axios";
function UsersComponent() { const [users, setUsers] = React.useState([]);
React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> axios <span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"https://reqres.in/api/users?page=2"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>res <span class="token operator">=></span> <span class="token function">setUsers</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>err <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"App"</span><span class="token operator">></span> <span class="token punctuation">{</span>users<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>aUser <span class="token operator">=></span> <span class="token punctuation">(</span> <span class="token operator"><</span>li<span class="token operator">></span> <span class="token operator"><</span>span<span class="token operator">></span> <span class="token punctuation">{</span>aUser<span class="token punctuation">.</span>first_name<span class="token punctuation">}</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token punctuation">{</span>aUser<span class="token punctuation">.</span>last_name<span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>span<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>li<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
}
This component is breaking the SRP. It has two responsibilities calling the api and rendering a list. Although it doesn’t look as bad but let’s say we get couple more requirements about how the list of user should be rendered. Let’s say we would check for if a user has an avatar if not then set them a default avatar. So now out component looks more like this
return ( <div className="App"> {users.map(aUser => ( <li> <span> {aUser.first_name}::{aUser.last_name} { users.avatar ? ( ...Show avatar ...Show some action for user
<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token operator">...</span><span class="token punctuation">.</span>Some Psuedo Code <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>span<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>li<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span>
);
So it is getting cumbersome and this is a good indication that we need to refactor this code. So we create a new UserListComponent
function usersList(props) {
const uploadAvatar = () => {
console.log("implement update avatar");
};
return (
<div>
{props.users.map(aUser => (
<li>
<span>
{aUser.first_name}::{aUser.last_name}
</span>
{aUser.avatar ? (
<img src={aUser.avatar} alt="" />
) : (
<button onClick={uploadAvatar}>Upload avatar</button>
)}
</li>
))}
</div>
);
}