12 April 2023
Do container components and React still go together? The container-presentational pattern in React vs hooks
Hooks this, hooks that – it seems that the whole React development these days is all about hooks. But perhaps the good ol’ containers are not a lost cause entirely? In this article, I’m going to show you why the container-presentational pattern is still viable and may even work in tandem with hooks for the best possible result in terms of performance, clarity, and reusability.
Containers may not be the hottest issue in the world right now – even in the world of React. But there’s still a very good case to be made for their use in 2023. In today’s article, you’re going to find out:
- what is the container-presentational pattern and when to use it,
- how it exactly works (I’m even going to make a really cool little app featuring Rick and Morty!),
- what the main benefits of the container-presentational pattern on both single developer and team level are,
- how it all relates to the ever-present React hooks.
So what’s the deal with the React containers?
Container components in React – are they still relevant?
The moment you saw the article’s title, you might have thought something like: what’s the point of using containers if I can just break down all the app logic into hooks?
The introduction of hooks by Facebook has completely changed the way React app development works. Hooks made it possible to use functional components in place of class components.
Functional components are easier to write and understand than class components. And if this wasn’t sufficiently attractive, they are also more efficient! No wonder so many devs swear by them as well as custom-made hooks!
A properly-written custom hook can move a large chunk of business logic and UI from the component to an abstraction layer. That’s a big part of why the code is so easy to read and maintain. But it may eventually grow too big for the hook to be a sufficient organizational tool. That’s where containers come in.
Sure, you might want to separate your business logic using hooks only. And if you are careful, it might even work. But because hooks can be used for logic and presentation, mixing them in is a little too easy. What containers offer is a clear distinction. And it really does matter in the long run.
Container components tend to really make a difference in large React apps. By combining separate container components with custom hooks, you can provide proper encapsulation and logic reusability in a way that is scalable and meets all the best practices of programming.
In order to understand why it matters so much that you do not mix logic and presentation in the same component, I’m going to present the container-presentational design pattern.
But before I get to the pattern itself, I’m going to briefly examine the topic of React design patterns. It will make the rest much easier to understand.
A developer explains: React lifecycle methods & hooks
Design patterns in React
Much like any other software tool you can think of, each React design pattern exists to solve very specific development problems.
A lot of React development challenges are fairly common and there is no point in trying to reinvent the wheel every time.
The patterns provide generic and reusable solutions to these challenges. They generalize and abstract away the challenge. They are properly tested and documented so that they can be applied to different contexts.
React design patterns benefits
When used properly, such design patterns offer a lot of benefits:
- Code standardization – when all the code is written and organized according to a given pattern, it’s predictable and easy to read for everyone.
- Time saving – as long as one knows the patterns used, one can easily understand the entire application. This understanding is transferable to each and every subsequent app built the same way.
- Code maintenance – separation of concerns and encapsulation means that testers know exactly where to search for a given bug, even as the app grows bigger and bigger.
It all sounds great, but before you can really feel all the benefits, you often need to invest a lot of time and effort in order to truly master a given React design pattern so that you are able to recreate it in different situations.
And here comes the one you’re been itching to learn more about…
The container-presentational pattern
The React design pattern is all about separating the business logic from the presentational layer, or if you’d rather: container components from the presentational components.
These components create and maintain data, pushing it further to their child components. They contain no UI elements. Their main goal is to deliver data and business logic to presentational components. It is a co-called smart component because it takes care of all the inner workings of the app in the background. Aside of smart components, we also have…
Their job is to make presentational data available to the UI. This data is first passed by their parent components. Components such as this can be described as dumb components because they don’t do anything all by themselves. They typically don’t have a local state unless they need it to display the UI in a specific way.
The container-presentational pattern made up of both presentational components and container components is abstract enough to find use across many different frontend frameworks other than React, including Angular and Vue.js.
Benefits of the container-presentational pattern
What difference does a code organization like this really make? To be perfectly honest, it can make all the difference, setting the app on a path of efficient and scalable growth over a long time. But to name just a few:
The Single Responsibility Principle (SRP) stipulated that each class should only have one responsibility. In this case, the container component takes care of the business logic, while the presentational component displays data. The pattern itself helps enforce that distinction. Code written like this is focused and concise, improving readability, which may come in handy during the onboarding of new devs to your project. This quality also makes it easier to achieve the remaining benefits.
Independent components are more reusable. When you create components this way, you can often save time when working on larger systems. The larger it is, the more of a difference it makes.
Improved modularity and scalability
When the pattern is applied, it’s easy to add and remove presentational components without affecting the app logic and states. It allows for building them in isolation, keeping track of their style and consistency. That’s what frontend tools such as Storybook were made for.
Isolated presentational components tend to have a rather simplistic structure. As a result, the QA tester can easily test them on a case-by-case basis. It’s known as snapshot testing or atomic testing.
The container-presentational pattern in practice – let’s make an app!
That’s it for the theory. Now is the time to make the promised Rick and Morty app.
Look at the App.tsx file I created. The component has:
- API data management state,
- data loading flag management state,
- API data fetching state,
- data presentation state.
At this point, even though it has a couple of different states, the component is still reasonably understandable. But what if I added a couple of API requests and presentational components? Not only will such a beast of a component be very complicated, but it will be very hard to test too.
What’s more, equipped with so many contextual dependencies, it is bound to lose any shred of reusability, which is one of the top-selling features of React.
It’s time to sort it all out. The new file structure will be as follows:
- The newly-created CharacterContainer.tsx file will be the new container.
- There will be two new presentational components – one will contain the CharacterList.tsx list and the other called Character.tsx will include a single list element.
Let’s take a closer look at CharacterContainer.tsx. It has all the states necessary for managing the app. It also fetches data and pushes it to the presentational component.
The CharacterList.tsx file accepts the data from parent components and displays a loading state as well as a list of characters.
The single-element Character.tsx file also gets its data from a parent and displays it.
This version has a couple of major advantages over the previous one:
- Full separation of business logic and presentational layer, making for code that is easy to understand, test, and document.
- High level of reusability will come in handy during development.
This is only a very simple example – all of these pros will be much more evident through the economies of scale in big commercial projects.
A question remains – can this code be even better? In order to answer it, let’s go back to the issue of hooks.
How to combine containers with hooks?
React provides a lot of different built-in hooks, including useState, useEffect, useContext, or use Memo. These hooks are essentially functions that allow you to use React features such as states in functional components. Developers can employ them for the purpose of state management, data processing, event handling, and many other React functionalities.
In order to complement my container-presentational solution, I’m going to build a custom hook.
Custom hooks in action
The gist of it is this special function useCharacters.tsx, which takes all the state management and data fetching logic. It returns data and a loading flag.
With that I can use a custom hook in CharacterContainer.tsx.
Yet again, I succeeded in separating business logic from the presentation component.
How well do you know custom hooks? Learn about best practices
The container-presentational pattern in React – lessons learned
And that’s it for the application. Hopefully, by now you can see that containers still have their place in the world of React development and can work alongside hooks to make your development more efficient at both an individual and team level.
To sum things up:
- Container-based approach with components is a good way to increase the efficiency, scalability, and modularity of a React application. Separating business logic from UI makes the code clearer while freeing visual components from data sources makes them reusable.
- For further efficiency improvements, you can move some logic from containers to custom hooks as well as use hooks within containers. By doing so, you get small pieces of business logic, which you can use in many places at the same time.
- Dividing code into functionalities like this is especially useful in large teams – individuals can work on separate functionalities in isolation.
Again rings true the maxim that each development challenge can be solved in many different ways. Some of these ways can be better in specific scenarios. Instead of stubbornly sticking to one way of solving a problem, choose the best one for your case and reap short and long-term benefits.
Are you searching for React developers who know how to use all the design patterns?
Write to us and let’s talk about the projects we made and the projects you want to make.