S.O.L.I.D: Embracing the Single Responsibility Principle in React

Introduction
In the world of software development, the SOLID principles have long been a guiding light towards creating software that is robust, maintainable, and flexible.
These principles, introduced by Robert C. Martin, provide a clear path to structure code in a way that is easy to understand, modify, and extend.
The SOLID acronym stands for:
- Single Responsibility Principle (SRP)
- Open-Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
While these principles were originally applied to object-oriented programming, they are equally valuable in the realm of modern front-end development, especially when working with a library like React.
In this article, we’ll focus on the first of these principles — the Single Responsibility Principle — and explore how it can guide us in creating better React components.
Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have one, and only one, reason to change. When applied to React, we can interpret this principle as “a component should ideally do one thing only”.
Adhering to the Single Responsibility Principle provides several benefits. It makes your components easier to understand, test, and debug. It also increases the reusability of your components, as a component that does one thing well can be used in various contexts.
Breaking Down Components
In React, we can achieve single responsibility by breaking down complex components into smaller, simpler ones.
Each component should encapsulate a specific part of the functionality. If a component is handling complex state logic, rendering multiple sections of the UI, and fetching data, it might be a good candidate to be split up.
Example: Before Applying SRP
let’s consider a BlogPost component that fetches a blog post data, handles comments, and also displays the post and comments.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function BlogPost({ postId }) {
const [post, setPost] = useState(null);
const [comments, setComments] = useState([]);
const [newComment, setNewComment] = useState('');
useEffect(() => {
async function fetchPost() {
const response = await axios.get(`/api/posts/${postId}`);
setPost(response.data);
}
fetchPost();
}, [postId]);
useEffect(() => {
async function fetchComments() {
const response = await axios.get(`/api/posts/${postId}/comments`);
setComments(response.data);
}
fetchComments();
}, [postId]);
const handleCommentChange = (event) => {
setNewComment(event.target.value);
};
const handleCommentSubmit = async (event) => {
event.preventDefault();
await axios.post(`/api/posts/${postId}/comments`, { text: newComment });
setNewComment('');
};
if (!post) return null;
return (
<div>
<h2>{post.title}</h2>
<p>{post.body}</p>
<h3>Comments</h3>
<ul>
{comments.map((comment) => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
<form onSubmit={handleCommentSubmit}>
<input
type="text"
value={newComment}
onChange={handleCommentChange}
/>
<button type="submit">Add Comment</button>
</form>
</div>
);
}In this example, the BlogPost component is responsible for fetching the post data, fetching the comments, handling the new comment input state, submitting new comments, and rendering the post and comments.
This component is clearly doing too much and not adhering to the Single Responsibility Principle.
Example: After Applying SRP
We can refactor this component to follow the Single Responsibility Principle by breaking it down into smaller components.
import React from 'react';
import { usePost } from './usePost'; // Custom hook to fetch post data
import { useComments } from './useComments'; // Custom hook to fetch and submit comments
function BlogPost({ postId }) {
const post = usePost(postId);
const { comments, newComment, handleCommentChange, handleCommentSubmit } = useComments(postId);
if (!post) return null;
return (
<div>
<Post post={post} />
<Comments
comments={comments}
newComment={newComment}
onCommentChange={handleCommentChange}
onCommentSubmit={handleCommentSubmit}
/>
</div>
);
}
function Post({ post }) {
return (
<div>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
);
}
function Comments({ comments, newComment, onCommentChange, onCommentSubmit }) {
return (
<div>
<h3>Comments</h3>
<CommentList comments={comments} />
<CommentForm
newComment={newComment}
onCommentChange={onCommentChange}
onCommentSubmit={onCommentSubmit}
/>
</div>
);
}
function CommentList({ comments }) {
return (
<ul>
{comments.map((comment) => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
function CommentForm({ newComment, onCommentChange, onCommentSubmit }) {
return (
<form onSubmit={onCommentSubmit}>
<input
type="text"
value={newComment}
onChange={onCommentChange}
/>
<button type="submit">Add Comment</button>
</form>
);
}In this refactored example, each component and custom hook has a single responsibility. Post is responsible for displaying the post, Comments is responsible for displaying and submitting comments, usePostis responsible for fetching the post data, and useComments is responsible for fetching and submitting comments.
The BlogPost component is now just coordinating these components and hooks based on the application's state. This makes the code cleaner, easier to understand, and each component and hook can be tested independently.
By adhering to the Single Responsibility Principle, we’ve made our components and hooks more reusable and maintainable. For instance, we could easily reuse the Post or Comments components in other parts of our application, or use the usePost and useComments hooks with different UI components.
This example illustrates how the Single Responsibility Principle can guide us in structuring our React applications for better readability, maintainability, and reusability.
Conclusion
The Single Responsibility Principle is a powerful guide that can help us create better, more maintainable React components. By ensuring that each component has a single responsibility, we can make our components easier to understand, test, and reuse.
While it might be tempting to create large, complex components that manage multiple aspects of an application, breaking these components down into smaller, single-responsibility components can greatly improve the quality of your codebase.
In the upcoming articles, we’ll continue our exploration of the SOLID principles in the context of React development.
We’ll look at how principles like the Open-Closed Principle and Liskov Substitution Principle can further enhance our React applications. Stay tuned!
Thank you so much for taking the time to read my article all the way through!
If you found it helpful or interesting, why not give it a round of applause by clicking those clap buttons? And hey, don’t miss out on more insightful content — hit that follow button to stay updated!
Let’s learn and grow together. Happy Coding! 👏





