Back to posts
Single Responsibility Principle

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:

  1. Manages a list of tasks.
  2. Handles adding and removing tasks.
  3. Updates UI state.
  4. 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::

  1. Improved Maintainability: Each component has a single responsibility, making it easier to modify without affecting other parts.

  2. Better Reusability: Components like TaskItem and TaskInput can be reused in other parts of the application.

  3. Easier Testing: Smaller components with focused responsibilities are easier to test in isolation.

  4. 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!

Additional Resources