Introduction

Software architecture is full of acronyms, but few are as cited—and as misunderstood—as SOLID. The first of these, the Single Responsibility Principle (SRP), states that "A class should have one, and only one, reason to change."

In the world of classic Object-Oriented Programming (Java, C#), this usually refers to classes. But in modern web development, where we deal with React components, utility modules, and API services, what does SRP actually look like?

I often see developers creating "God Objects" or "God Components"—files that do everything from fetching data and formatting dates to rendering UI and handling complex validation logic. These files are a nightmare to test and impossible to refactor. Today, we will look at how to apply SRP to write cleaner, more modular JavaScript.

Defining "Reason to Change"

The definition "one reason to change" is abstract. Let's make it concrete.

Imagine you have a UserManager class. It handles authentication logic. Suddenly, your boss asks you to change the email format for the "Welcome" email sent upon signup. If you have to open UserManager.js to change the email HTML, you have violated SRP.

Why? Because the UserManager now has two reasons to change:

  1. Changes in authentication logic (e.g., switching from tokens to cookies).
  2. Changes in communication logic (e.g., email templates).

A Practical Refactoring Example

Let's look at a typical violation of SRP in a JavaScript class that handles a shopping cart.

// ❌ Bad: Mixed Responsibilities
class ShoppingCart {
    constructor() {
        this.items = [];
    }

    addItem(item) {
        this.items.push(item);
        this.logActivity('Item added: ' + item.name);
    }

    calculateTotal() {
        return this.items.reduce((sum, item) => sum + item.price, 0);
    }

    printInvoice() {
        console.log("---------------");
        console.log("Invoice Date: " + new Date().toISOString());
        console.log("Total: " + this.calculateTotal());
        console.log("---------------");
    }

    logActivity(message) {
        // Imagine this writes to a remote server or local storage
        console.log("LOG: " + message);
    }
}

This class is doing too much. It manages the state of the cart, it calculates business logic (totals), it handles presentation (printing invoices), and it handles infrastructure (logging).

If we want to change the log format, we risk breaking the cart logic. If we want to change the invoice output to PDF instead of console, we have to touch the core cart class.

Applying SRP

Let's break this down into small, focused classes or modules.

// ✅ Good: Separated Concerns

// 1. Core Domain Logic
class Cart {
    constructor() {
        this.items = [];
    }

    add(item) {
        this.items.push(item);
    }

    getItems() {
        return this.items;
    }
}

// 2. Business Logic / Calculator
class CartCalculator {
    static calculateTotal(items) {
        return items.reduce((sum, item) => sum + item.price, 0);
    }
}

// 3. Presentation / Output
class InvoicePrinter {
    static print(cartItems, total) {
        console.log("--- Invoice ---");
        cartItems.forEach(item => console.log(`- ${item.name}: $${item.price}`));
        console.log(`Total: $${total}`);
    }
}

// 4. Infrastructure / Logging
class Logger {
    static log(message) {
        console.log(`[System Log]: ${message}`);
    }
}

Now, we can glue these together in a controller or a main function:

// Orchestration
const myCart = new Cart();
const item = { name: "Keyboard", price: 100 };

myCart.add(item);
Logger.log("Item added to cart");

const total = CartCalculator.calculateTotal(myCart.getItems());
InvoicePrinter.print(myCart.getItems(), total);

This might look like more code initially, but the benefits are immense:

SRP in React Components

The same logic applies to frontend components. A common anti-pattern is the component that fetches data, parses it, validates forms, and renders the UI.

To apply SRP in React:

Conclusion

The Single Responsibility Principle is about organizing code for change. In software development, the only constant is change. By keeping your functions, classes, and components focused on doing one thing well, you build a system that accepts change gracefully rather than breaking brittle dependencies.

Next time you write a function, ask yourself: "What are the reasons this function might need to be rewritten?" If the answer is "If the API changes OR if the design changes," you should probably split it up.

← Back to Architecture Articles

Back to Home