DEV Community

Prasanna Vijayan
Prasanna Vijayan

Posted on

The Container-Presenter Pattern in React: A Smart Approach to Component Design

Introduction

As front-end applications grow in complexity, maintaining a clean and scalable architecture becomes essential. One of the most effective design patterns to achieve this is the Container-Presenter Pattern, also known as the Smart-Dumb Components Pattern. This pattern helps separate business logic from UI rendering, making components more reusable, maintainable, and easier to test.

In this blog, we'll explore the Container-Presenter Pattern, why it's beneficial, and how to implement it in React.


What is the Container-Presenter Pattern?

The Container-Presenter Pattern is a design approach that splits components into two distinct types:

  1. Container Components (Smart Components): Responsible for state management, business logic, and API calls. They do not handle UI rendering.
  2. Presenter Components (Dumb Components): Focus purely on displaying UI and receiving data via props. They do not handle state or side effects.

This pattern ensures that UI components remain pure and reusable, while logic-heavy components manage data efficiently.


Why Use the Container-Presenter Pattern?

✅ Separation of Concerns
Keeps UI and business logic separate, making the codebase cleaner and easier to maintain.

✅ Reusability
Presenter components are independent of state, so they can be reused across multiple parts of the application.

✅ Better Testing

  1. Presenter components are easier to test as they are pure functions of their props.
  2. Containers can be tested separately for state management and API calls.

✅ Scalability
Makes it easier to scale large applications without cluttering UI components with logic.


Implementing the Container-Presenter Pattern in React

Let's break down the pattern with a real-world example: Fetching and displaying a list of todo items.

Step 1: Create the Presenter (Dumb) Component

This component only renders the list of todo items and receives data via props.

import React from "react";

const TodoList = ({ items }) => {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.title}</li>
      ))}
    </ul>
  );
};

export default TodoList;
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Container (Smart) Component

This component fetches data and passes it down to the Presenter component.

import React, { useState, useEffect } from "react";
import TodoList from "./TodoList";

const TodoListContainer = () => {
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch("https://api.json.com/checklist")
      .then((response) => response.json())
      .then((data) => setItems(data));
  }, []);

  return <TodoList items={items} />;
};

export default TodoListContainer;
Enter fullscreen mode Exit fullscreen mode

Step 3: Use the Container Component in Your App

import React from "react";
import TodoListContainer from "./TodoListContainer";

const App = () => {
  return (
    <div>
      <h1>Todo List</h1>
      <TodoListContainer />
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

When to Use the Container-Presenter Pattern?

✔️ Good Use Cases:

When separating UI from business logic makes sense.

When the UI component is used in multiple places.

When you need better testability.

When working on scalable applications.

❌ When NOT to Use It:

When the component is small and simple (splitting adds unnecessary complexity).

When using hooks-based state management that keeps logic within reusable hooks.

When the framework already manages state elegantly (e.g., Vue’s reactivity system).


Alternative Approaches

Besides the Container-Presenter Pattern, here are some other useful patterns in React:

  1. Hooks-Based Pattern (Using useState, useEffect, and useReducer to manage logic separately).
  2. State Machines (XState) (For managing complex UI states).
  3. Context API / Redux (For global state management).

Final Thoughts

The Container-Presenter Pattern is a powerful way to separate concerns and keep your React components modular, testable, and maintainable. While it’s an excellent approach for larger applications, you should assess whether it adds value to your specific use case.

By structuring your components with clear responsibilities, you ensure a scalable and maintainable front-end architecture.

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay