
Single Responsibility Principle
Matan Shaviro / February 20, 2025
Introduction
The Single Responsibility Principle (SRP) is one of the key principles in software design, stating that a component or module should have only one reason to change. In React, following SRP leads to cleaner, maintainable, and more reusable components.
In this blog post, we will explore SRP by refactoring a Task Manager component into smaller, well-defined components, each handling a single responsibility.
What is the Single Responsibility Principle?
SRP dictates that a component should only handle one responsibility. If a component does multiple things—like fetching data, managing UI state, and rendering—it becomes difficult to maintain and reuse.
Problems with a Non-SRP Component
Consider a Task Manager component that:
- Manages a list of tasks.
- Handles adding and removing tasks.
- Updates UI state.
- Manages local storage persistence.
This violates SRP because it has multiple reasons to change. Let's refactor it!
Refactoring a Task Manager Component
We'll break down this behemoth into smaller, more manageable components.
Step 1: Define a Task
Type
First, let's define what a Task
looks like in our application. This helps us
keep our data consistent.
type Task = {
id: number
title: string
completed: boolean
}
Step 2: Create a TaskList
Component
Now, we'll create a component specifically for displaying the list of tasks.
const TaskList = ({ tasks }: { tasks: Task[] }) => {
return (
<ul className='task-list'>
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</ul>
)
}
Step 3: Create a TaskItem
Component
This component handles the rendering of each individual task.
const TaskItem = ({
task,
onToggle,
onDelete
}: {
task: Task
onToggle: (id: number) => void
onDelete: (id: number) => void
}) => {
return (
<li className='task-item'>
<input
type='checkbox'
checked={task.completed}
onChange={()=> onToggle(task.id)}
/>
<span className={task.completed ? 'completed' : ''}>{task.title}</span>
<button onClick={()=> onDelete(task.id)}>Delete</button>
</li>
)
}
Step 4: Create a TaskInput
Component
This component is responsible for handling the input and adding new tasks.
const TaskInput = ({ onAdd }: { onAdd: (title: string) => void }) => {
const [title, setTitle] = useState('')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (title.trim()) {
onAdd(title)
setTitle('')
}
}
return (
<form onSubmit={handleSubmit}>
<input
type='text'
value={title}
onChange={e=> setTitle(e.target.value)}
placeholder='Add a new task'
/>
<button type='submit'>Add Task</button>
</form>
)
}
Step 5: Create a Custom Hook for Task Logic
We'll extract all the task-related logic into a custom hook. This makes our components cleaner and easier to test.
const useTaskManager = () => {
const [tasks, setTasks] = useState<Task[]>([])
const addTask = (title: string) => {
const newTask: Task = {
id: Date.now(),
title,
completed: false
}
setTasks([...tasks, newTask])
}
const toggleTask = (id: number) => {
setTasks(
tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
)
)
}
const deleteTask = (id: number) => {
setTasks(tasks.filter(task => task.id !== id))
}
return { tasks, addTask, toggleTask, deleteTask }
}
Step 6: Put It All Together
Finally, we bring all the pieces together in our main TaskManager component.
const TaskManager = () => {
const { tasks, addTask, toggleTask, deleteTask } = useTaskManager()
return (
<div className='task-manager'>
<h1>Task Manager</h1>
<TaskInput onAdd={addTask} />
<TaskList tasks={tasks} onToggle={toggleTask} onDelete={deleteTask} />
</div>
)
}
The Joy of SRP: What Did We Gain?
By breaking down our Task Manager, we've made our code::
-
Improved Maintainability: Each component has a single responsibility, making it easier to modify without affecting other parts.
-
Better Reusability: Components like
TaskItem
andTaskInput
can be reused in other parts of the application. -
Easier Testing: Smaller components with focused responsibilities are easier to test in isolation.
-
Enhanced Readability: The code is more organized and easier to understand.
Conclusion
The Single Responsibility Principle is your friend. It's a simple but powerful tool that can dramatically improve the quality of your React code. So, the next time you find yourself wrestling with a complex component, remember SRP. Break it down, keep it simple, and enjoy the benefits of cleaner, more maintainable code!