How To Use Single Responsibility Principle in ReactJS?
Last Updated :
08 Jun, 2021
If you're a developer then surely you might have listened to SOLID Principles word several times in your programming career. In Software development, the SOLID principle works as a guideline for developers. It doesn't matter which language you're using in your project, to make your code clean and maintainable, you need to apply the SOLID principle in your project.Â
SOLID principle makes the task easier for developers, and also it helps them in maintaining the code in their project. Let's talk about React now, a very popular framework among developers.Â
With the help of React, you can create a beautiful UI. In the earlier stage of your career, you may do a lot of mistakes in writing the code in React but once you will have experience in working on it, you will understand that it's also important to write clean and maintainable code in React. For this reason, surely one thing that can help you are the SOLID principle.Â
You can write small, beautiful, and clean React components. SOLID principle makes your component visible with clear responsibilities. The SOLID principle tells us that each class should have a single purpose of existence. In React, components should do only one thing at a time.Â
Now let's understand how to refactor a bad code in React and make it cleaner. First, let's consider a bad example...
JavaScript
import React, {useEffect, useReducer, useState} from "react";
const initialState = {
isLoading: true
};
// COMPLEX STATE MANAGEMENT
function reducer(state, action) {
switch (action.type) {
case 'LOADING':
return {isLoading: true};
case 'FINISHED':
return {isLoading: false};
default:
return state;
}
}
export const SingleResponsibilityPrinciple = () => {
const [users , setUsers] = useState([])
const [filteredUsers , setFilteredUsers] = useState([])
const [state, dispatch] = useReducer(reducer, initialState);
const showDetails = (userId) => {
const user = filteredUsers.find(user => user.id===userId);
alert(user.contact)
}
// REMOTE DATA FETCHING
useEffect(() => {
dispatch({type:'LOADING'})
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(json => {
dispatch({type:'FINISHED'})
setUsers(json)
})
},[])
// PROCESSING DATA
useEffect(() => {
const filteredUsers = users.map(user => {
return {
id: user.id,
name: user.name,
contact: `${user.phone} , ${user.email}`
};
});
setFilteredUsers(filteredUsers)
},[users])
// COMPLEX UI RENDERING
return <>
<div> Users List</div>
<div> Loading state: {state.isLoading? 'Loading': 'Success'}</div>
{users.map(user => {
return <div key={user.id} onClick={() => showDetails(user.id)}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
</>
}
Here, we are fetching the data from the remote source, and then we are rendering it in the UI. We are also detecting the loading state of the API call. Basically, the above code is divided into mainly...four things...
- Remote data fetching...
- Data filtering...
- Complex state management...
- Complex UI functionality...
Now let's see how to improve the design of this code and how to make it more cleanable...
1. Separating Data Processing Logic From the Code.
You should never keep your HTTP calls inside the component. This is a basic rule of thumb. To remove these codes from the component, you can follow several strategies.Â
You can create a custom hook, and you can move your data fetching and filtering logic inside that custom hook. Lets' see how to do this...
Create a hook named useGetRemoteData. It looks like below...
JavaScript
import {useEffect, useReducer, useState} from "react";
const initialState = {
isLoading: true
};
function reducer(state, action) {
switch (action.type) {
case 'LOADING':
return {isLoading: true};
case 'FINISHED':
return {isLoading: false};
default:
return state;
}
}
export const useGetRemoteData = (url) => {
const [users , setUsers] = useState([])
const [state, dispatch] = useReducer(reducer, initialState);
const [filteredUsers , setFilteredUsers] = useState([])
useEffect(() => {
dispatch({type:'LOADING'})
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(json => {
dispatch({type:'FINISHED'})
setUsers(json)
})
},[])
useEffect(() => {
const filteredUsers = users.map(user => {
return {
id: user.id,
name: user.name,
contact: `${user.phone} , ${user.email}`
};
});
setFilteredUsers(filteredUsers)
},[users])
return {filteredUsers , isLoading: state.isLoading}
}
Now if you look at your main component then it will look like this...
JavaScript
import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";
export const SingleResponsibilityPrinciple = () => {
const {filteredUsers , isLoading} = useGetRemoteData()
const showDetails = (userId) => {
const user = filteredUsers.find(user => user.id===userId);
alert(user.contact)
}
return <>
<div> Users List</div>
<div> Loading state: {isLoading? 'Loading': 'Success'}</div>
{filteredUsers.map(user => {
return <div key={user.id} onClick={() => showDetails(user.id)}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
</>
}
You can observe that your component is much cleaner and easier to understand now. Let's make our code much better using some more techniques or methods.
2. Separate the Code of Data Fetching to Make it Reusable
useGetRemoteData is serving two purposes in your code...
- Fetching data from a remote source
- Filtering data
We can make a separate hook, and we can move our data fetching logic there. Let's give it the name...useHttpGetRequest. It takes the URL as a component.
JavaScript
import {useEffect, useReducer, useState} from "react";
import {loadingReducer} from "./LoadingReducer";
const initialState = {
isLoading: true
};
export const useHttpGetRequest = (URL) => {
const [users , setUsers] = useState([])
const [state, dispatch] = useReducer(loadingReducer, initialState);
useEffect(() => {
dispatch({type:'LOADING'})
fetch(URL)
.then(response => response.json())
.then(json => {
dispatch({type:'FINISHED'})
setUsers(json)
})
},[])
return {users , isLoading: state.isLoading}
}
Let's also separate the reducer logic into a separate file...
Â
JavaScript
export function loadingReducer(state, action) {
switch (action.type) {
case 'LOADING':
return {isLoading: true};
case 'FINISHED':
return {isLoading: false};
default:
return state;
}
}
After performing the above two operations...useGetRemoteData looks like below...
Â
JavaScript
import {useEffect, useState} from "react";
import {useHttpGetRequest} from "./useHttpGet";
const REMOTE_URL = 'https://jsonplaceholder.typicode.com/users'
export const useGetRemoteData = () => {
const {users , isLoading} = useHttpGetRequest(REMOTE_URL)
const [filteredUsers , setFilteredUsers] = useState([])
useEffect(() => {
const filteredUsers = users.map(user => {
return {
id: user.id,
name: user.name,
contact: `${user.phone} , ${user.email}`
};
});
setFilteredUsers(filteredUsers)
},[users])
return {filteredUsers , isLoading}
}
Now you can observe that the code becomes much cleaner. We can perform some more operations and make this code much better. Let's see that how to do this...
3. Decompose UI Components
Separate the code of user details into a different component which is only responsible to display the UserDetails.
JavaScript
const UserDetails = (user) => {
const showDetails = (user) => {
alert(user.contact)
}
return <div key={user.id} onClick={() => showDetails(user)}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
}
Now the original component looks like below:
JavaScript
import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";
export const Users = () => {
const {filteredUsers , isLoading} = useGetRemoteData()
return <>
<div> Users List</div>
<div> Loading state: {isLoading? 'Loading': 'Success'}</div>
{filteredUsers.map(user => <UserDetails user={user}/>)}
</>
}
Did you observe that how your code which was too long is now too short? We just decomposed the code into five separate components, and we put our logic over there. Each component is now responsible for single responsibility.
Let's review our code and see what we did here. We created five different components...
- Users.js: Responsible for displaying the user list.
- UserDetails.js: Responsible for displaying details of a user
- useGetRemoteData.js: Responsible for filtering remote data
- useHttpGetrequest.js: Responsible for HTTP calls
- LoadingReducer.js: Complex state management.
Hope things are clear to you now.
Similar Reads
How to Use Flux to Manage State in ReactJS? State management in ReactJS is important for building dynamic and responsive applications. Flux, an architecture for managing state, provides a structured approach to handle data flow and state changes efficiently in React applications. In this article, we will explore the Flux to Manage State in Re
5 min read
How to lift state two levels up in ReactJS ? We want to lift the state two levels up such that when an event occurs at the lower level component then it should affect the state two levels up. For example, there are three components X, Y, and Z. X is the parent of Component Y, and Component Y is the parent of Component Z. Prerequisites:NodeJS o
2 min read
Introduction to Recoil For State Management in React State Management is a core aspect of React development, especially as applications grow in size and complexity. While there are many libraries available to handle state, recoil has emerged as the fresh, modern approach that simplifies state management without the bloat of more complex systems like R
6 min read
How to Work with and Manipulate State in React ? Working with and Manipulating state in React JS makes the components re-render on the UI by Updating the DOM tree. It makes sure to render the latest information and data on the interface. Prerequisites:NPM & Node.jsReact JSState in React JSReact JS Class componentsReact JS Functional Components
6 min read
How to pass multiple props in a single event handler in ReactJS? To pass multiple props in a single event handler in ReactJS we can create a function to handle the event that accepts multiple props or calls multiple methods. It is a normal task while creating a react project to perform multiple operations for some events. PrerequisitesReact JSReact JS PropsTo pas
2 min read
How to use HOCs to reuse Component Logic in React ? In React, making reusable components keeps your code neat. Higher-order components (HOCs) are a smart way to bundle and reuse component logic. HOCs are like magic functions that take a component and give you back an upgraded version with extra powers or information. HOCs can be implemented in a few
4 min read
How to locally manage component's state in ReactJS ? Any component in React JS majorly depends on its props and state to manage data. A component's state is private to it and is responsible for governing its behavior throughout its life. A state is nothing but a structure that records any data changes in a react application. It can be used for storing
2 min read
How to do CRUD operations in ReactJS ? CRUD (Create, Read, Update, Delete) CRUD (Create, Read, Update, Delete) operations are fundamental for managing data in applications. In ReactJS, you can easily perform CRUD operations by manipulating local state, using forms for data input, and integrating with local storage for persistent data.In
9 min read
How to Learn ReactJS in 2025? npx create-react-app myappnpm startnpm run buildnpm installAren't all the above commands familiar to you? If yes, then you might be working on React, or you might have started working on this amazing JavaScript Library. If you're a passionate developer, then surely you might be aware of the populari
10 min read
How to set Parent State from Children Component in ReactJS? To set the parent state from a child component, we use Reactâs unidirectional data flow. Instead of directly passing data, we pass a function that enables the child to send data back to set the parent state.Prerequisites:React JSuseState HookApproachTo set parent state from child component in React,
2 min read