The Container/Presentational Pattern: Simplifying Front-End Development with Separation of Concerns

Alex U
5 min readMar 22, 2023

--

Whether you’re a seasoned JavaScript developer or just starting out, understanding the principles of the Container/Presentational pattern can help you build a cleaner, more organized code that is easier to maintain and scale over time. By separating the concerns of managing state and rendering UI, this pattern helps developers avoid common pitfalls like tight coupling, spaghetti code, and bloated components. So, let’s dive in and explore the world of the Container/Presentational pattern, framework-agnostic style!

Photo by Adrian Trinkaus on Unsplash

The Container/Presentational pattern is a popular design pattern used in front-end development to simplify the management of application state and UI rendering. The pattern involves breaking down the components of the application into two categories: the Container components and the Presentational components.

The Container components, as the name suggests, are responsible for managing the application state. They retrieve the data from the backend and pass it to the Presentational components. They also dispatch actions that update the state when an event occurs. The Container components are often connected to a state management library like Redux, which helps manage the state in a centralized location.

The Presentational components, on the other hand, are responsible for rendering the UI based on the data provided by the Container components. They are often reusable and independent of the state of the application. This makes them easy to test and maintain.

The main benefit of using the Container/Presentational pattern is the separation of concerns it provides. By separating the state management and UI rendering, it becomes easier to understand and maintain the code. This approach also helps in achieving better code reusability and scalability. For instance, the Presentational components can be reused in other parts of the application or in other applications altogether.

Another advantage of the pattern is that it promotes a cleaner and more organized codebase. By clearly separating the concerns, it becomes easier for multiple developers to work on the same project without interfering with each other’s work.

However, there are also some downsides to this pattern. One of the main concerns is the additional layer of indirection it introduces, which can make the code more complex. Moreover, it can be more difficult to learn and implement than other simpler approaches to managing application state and rendering UI.

When writing an article on a technical topic, it’s important to make sure the information is accessible to as many readers as possible. For this reason, I’ve chosen to avoid using a specific JavaScript framework in this article. Instead, I’ll be focusing on the core principles of the Container/Presentational pattern, which can be applied in any JavaScript project, regardless of the framework or library being used.

Here’s an example of how the Container/Presentational pattern can be used with vanilla JavaScript:

Suppose we have a simple application that displays a list of products and allows users to add new products. The application consists of two main components: a list of products component and a form component.

The container component, which manages the state and logic of the application, might look like this:

const ProductContainer = (() => {
const products = [];

const addProduct = (name, price) => {
products.push({ name, price });
};

const init = () => {
// Fetch the list of products from an API
// and render the UI.
render();
};

const render = () => {
const productList = new ProductList(products);
const productForm = new ProductForm(addProduct);

productList.render();
productForm.render();
};

return {
init,
};
})();

ProductContainer.init();

In this example, ProductContainer is responsible for managing the state of the application. It defines an addProduct function that adds a new product to the products array, and an init function that initializes the application by fetching the list of products from an API and rendering the UI.

The presentational components, which are responsible for rendering the UI based on the props they receive, might look like this:

ProductForm:

class ProductForm {
constructor(onSubmit) {
this.onSubmit = onSubmit;
}

render() {
const form = document.createElement('form');
const nameInput = document.createElement('input');
const priceInput = document.createElement('input');
const submitButton = document.createElement('button');

form.addEventListener('submit', (event) => {
event.preventDefault();

const name = nameInput.value;
const price = priceInput.value;

this.onSubmit(name, price);

// Clear the form inputs
nameInput.value = '';
priceInput.value = '';
});

nameInput.type = 'text';
nameInput.placeholder = 'Product name';
priceInput.type = 'text';
priceInput.placeholder = 'Price';
submitButton.type = 'submit';
submitButton.textContent = 'Add product';

form.appendChild(nameInput);
form.appendChild(priceInput);
form.appendChild(submitButton);

document.body.appendChild(form);
}
}

ProductList:

class ProductList {
constructor(products) {
this.products = products;
}

render() {
const list = document.createElement('ul');

this.products.forEach((product) => {
const item = document.createElement('li');
const name = document.createElement('h3');
const price = document.createElement('p');

name.textContent = product.name;
price.textContent = product.price;

item.appendChild(name);
item.appendChild(price);
list.appendChild(item);
});

document.body.appendChild(list);
}
}

In this example, ProductForm and ProductList are presentational components that receive props from ProductContainer. ProductForm renders a form that allows the user to input a new product, and calls the onSubmit function when the form is submitted. ProductList renders a list of products based on the products that are passed in as props.

By using the Container/Presentational pattern, we have separated the concerns of managing the state and rendering the UI into two distinct components. This makes it easier to understand and maintain the code, and allows us to reuse the presentational components in other parts of the application if needed.

Pros:

  1. Separation of concerns: The pattern separates the concerns of managing the application state and rendering the UI, making it easier to understand and maintain the code.
  2. Reusability: The presentational components can be reused in other parts of the application or in other applications, as they are not tightly coupled to the application state.
  3. Testability: The presentational components are easy to test, as they only depend on their props and do not have any side effects.
  4. Scalability: The pattern can help manage the complexity of large applications by breaking them down into smaller, more manageable components.

Cons:

  1. Indirection: The pattern introduces an additional layer of indirection between the state and the UI, which can make the code more complex.
  2. Learning curve: The pattern can be more difficult to understand and implement than simpler approaches to managing the application state.
  3. Over-engineering: The pattern can be over-engineered for smaller applications or simple UIs, leading to unnecessary complexity and reduced developer productivity.
  4. Boilerplate code: The pattern can lead to the creation of additional boilerplate code, especially when using a state management library like Redux.

Overall, the Container/Presentational pattern is a powerful tool for managing the complexity of large applications and building reusable UI components. However, it may not be the best approach for smaller applications or simple UIs, and should be used judiciously to avoid unnecessary complexity.

References:

--

--