Separation of Concerns in React – Simple, yet difficult

Introduction

React, as a popular JavaScript library for building user interfaces, provides developers with the flexibility and power to create complex applications. However, as your React applications grow in size and complexity, you might encounter a situation where you need to reuse a component, but it proves to be far more complicated than it should be. This often happens when a component is burdened with complex logic that not only affects its own behavior but also ripples into the behavior of other components, making it challenging to recycle the component while reusing its user interface.

So how can we solve this problem?

The solution to this problem lies in understanding and implementing the concept of “Separation of Concerns.” In this article, we’ll explore what separation of concerns means in software development and how it can be applied in React to make your code more maintainable, reusable, and scalable.

What is the separation of concerns?

In the realm of software development, separation of concerns is a fundamental principle. It is all about ensuring that each component of your program serves a single, well-defined purpose. In the context of React, this separation involves dividing your code into two main categories: the logic or data management part and the user interface presentation part.

By splitting the logic from the view, you can create a clear distinction between how your application works and how it appears to the user. This clear separation has several advantages:

  • Maintainability: It becomes easier to find, modify, and debug code when logic and presentation are separate.
  • Reusability: You can reuse presentation components with different data sources, and you can reuse logic components in various parts of your application.
  • Scalability: As your application grows, you can manage it more effectively by keeping logic and presentation components separate.

This is where the “Container and Presentation Component” pattern comes into play, helping to achieve the separation of concerns. Let’s dive deeper into this pattern.

Container components and Presentation components

An example

In the example provided, there is a component named UIController that consists of a label and a reset button. The benefit of this component is that it can be used in multiple locations to modify other properties such as Font Style and Text Transform. The component also contains a function called handleResetValue. Now I want to use this component somewhere else, but with a different reset method and it doesn’t care about the styleKey. How can I achieve it? Well, just write a new UIController component or add a custom prop to the existing one ?

Okay, that’s fine, it solves your problem, but it makes a duplicated UI component. To make this possible, here’s the key: do not put any logic code inside the UIController component. Instead, create a separate container component that handles the logic, and pass the necessary data and functions as props to the UIController.

To use the component simply add it to the parent component

Understanding Container and Presentation Components

In React, container components and presentation components play distinct roles:

  • Container Components: These components are responsible for data fetching, state management, and logic. They act as intermediaries between the data and the presentation components. Container components fetch data from APIs, manage the state of your application, and pass data and functions to the presentation components.
  • Presentation Components: These components focus on rendering UI elements and displaying data from container components. They have no awareness of where the data comes from or how it is manipulated; their sole purpose is to present the data and interact with the user.

This separation is a simple yet powerful concept that ensures a clean and maintainable codebase. By adhering to this pattern, you can avoid common mistakes that many developers make when starting with React, such as mixing logic and presentation in a single component. Such mistakes can lead to unwieldy and error-prone code.

Easy right?

The above example may seem basic, but many developers still make mistakes, I was one of them before. These mistakes can cause things to go awry. I have come across many instances of code like this, and what I had to do was create another component to achieve reusability for the existing component.

More examples

We are going to create a Countdown Timer component that will include a timer, a start button and a stop button.

First, let’s create the Presentation components:

TimerDisplay component:

StartButton component:

StopButton component:

Now we can create a Container component to wrap all the UI components

As you can see, the component contains all UI components. I have implemented a hook called useCountdownTimer to handle the logic. Let’s write this out:

We have completed the Countdown Timer, but there is a minor performance issue. You can fix it using useCallback and memo from React. I won’t mention those two parts in this article. You can learn more in the React documentation.

Challenges and Common Mistakes

While the concept of separation of concerns is relatively straightforward, developers often face challenges when implementing it in their React projects. Here are some common pitfalls and how to avoid them:

  • Overcomplicating the Separation: Striking the right balance between container and presentation components can be challenging. Avoid creating too many container components that wrap presentation components excessively, as it can lead to unnecessary complexity.
  • State management: Handling state in a React application can be tricky, especially when dealing with deeply nested components. A state management library can help manage state in a more organized way (redux, global state, recoil,…)
  • Communication Between Components: Passing data and functions from container components to presentation components can be cumbersome. Use props, context, or third-party libraries of state management to streamline data flow.

Conclusion

In React development, thinking about reusability is a positive step, but it’s equally important to consider maintainability and scalability. The separation of concerns through container and presentation components is a valuable technique that simplifies the development process, fosters code reusability and enhances codebase maintainability.

By applying this principle, you can efficiently manage complex React applications, make your code more modular, and build scalable applications that are easier to maintain and extend.

As you continue your journey in React development, remember to prioritize the separation of concerns. Your future self (and your fellow developers) will thank you for it.

ThanhLM

Comments

Let’s make a great impact together

Be a part of BraveBits to unlock your full potential and be proud of the impact you make.