DEV Community

Cover image for React Advanced: Decoupling your components in the right way
Riccardo Tartaglia
Riccardo Tartaglia

Posted on • Updated on

React Advanced: Decoupling your components in the right way

Every developer's dream is to write less code and, if possible, make it all reusable.

In React, this translates into knowing how to properly decouple the logic of a component from its presentation.

Easier said than done, right?

In this article, I'll show you how to effectively decouple your components to make your code extremely reusable.
Before we get started, let's take a look at the fundamental concept of "coupling."

Coupling

In computer science, coupling is a concept that denotes the dependence between two or more components. For instance, if a component A depends on another component B, then A is said to be coupled with B.

Coupling is the enemy of change because it ties together things that can change in parallel.

This makes it extremely challenging to modify a single point in your application. Touching a single component can introduce anomalies in various parts of the application.

You must spend time tracking down all the parts that need to be modified, or you'll find yourself wondering why everything has gone haywire.

If we view a React component as a pure presentation element, we can say that it can be coupled with many things:

  • Business logic that determines its behavior (hooks, custom hooks, etc.).
  • External services (APIs, databases, etc.).
  • Another React component (for example, a component responsible for managing the state of a form).

This tight coupling, when modified, can lead to unpredictable side effects on other parts of the system.

Let's take a closer look at this component.

import { useCustomerHook } from './hooks';

const Customer = () => {
  const { name, surname } = useCustomerHook();
  return (
    <div>
      <p>{name}</p>
      <p>{surname}</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

It all seems fine at first glance, but in reality, we have a problem: this component is coupled with the custom hook useCustomerHook, which retrieves customer data from an external service. Therefore, our Customer component is not a "pure" component because it depends on logic that is not solely related to presenting its UI.

Now, let's consider that the custom hook useCustomerHook is also used in other components. What can we expect if we decide to modify it? Well, we should brace ourselves for quite a bit of work because we'll have to modify all the components that use it and are coupled with it.

Decoupling the Logic of a React Component

Let's revisit the previous example. I mentioned that the Customer component is coupled with the custom hook useCustomerHook, which applies fetching logic to retrieve customer data.

Now, let's explore how to decouple the logic of this component, so that we can transform it into a pure presentation component.

import { useCustomerHook } from './hooks';

const Customer = ({name, surname}) => {
  return (
    <div>
      <p>{name}</p>
      <p>{surname}</p>
    </div>
  );
};

const CustomerWrapper = () => {
  const { name, surname } = useCustomerHook();
  return <Customer name={name} surname={surname} />;
};

export default CustomerWrapper;
Enter fullscreen mode Exit fullscreen mode

Now, the Customer component is indeed a pure presentation component because it no longer uses useCustomerHook and only handles UI logic.

I've employed a wrapper component to decouple the logic of the Customer component. This technique is known as Container Components and allows us to modify the UI of our component without worrying about "breaking" the underlying logic.

Customer now only needs to concern itself with displaying presentation information. All the necessary variables are passed as props, making it easy to nest it anywhere in our code without concerns.
However, I'm still not satisfied for two reasons:

  1. The CustomerWrapper component is still coupled with the custom hook. So, if I decide to modify it, I would still need to modify the wrapper component.
  2. I had to create an extra component, CustomerWrapper, to decouple the logic, which means I've written a bit more code. We can address these two issues by using composition.

Composition

In computer science, composition is a concept that refers to combining two or more elements to create a new one. For example, if we have two functions f and g, we can compose them to create a new function h, which is the composition of f and g.

const f = (x) => x + 1;
const g = (x) => x * 2;
const h = (x) => f(g(x));
Enter fullscreen mode Exit fullscreen mode

We can apply the same concept to custom hooks as well. In fact, we can compose two or more custom hooks to create a new one.

const useCustomerHook = () => {
  const { name, surname } = useCustomer();
  const { age } = useCustomerAge();
  return { name, surname, age };
};
Enter fullscreen mode Exit fullscreen mode

In this way, the custom hook useCustomerHook is composed of the custom hooks useCustomer and useCustomerAge.

By using composition, we can decouple the logic of a React component without having to create a wrapper component. To apply composition conveniently, we use the library react-hooks-compose

Let's see how we can apply composition to our example.

import composeHooks from 'react-hooks-compose';
import { useCustomerHook } from './hooks';

const Customer = ({name, surname}) => {
  return (
    <div>
      <p>{name}</p>
      <p>{surname}</p>
    </div>
  );
};

export default composeHooks({useCustomerHook})(Customer);
Enter fullscreen mode Exit fullscreen mode

Now, the Customer component is indeed a pure presentation component. It's not coupled with any custom hooks and only handles UI logic. Furthermore, you didn't have to create any additional components to decouple the logic. In fact, composition allows you to create a cleaner and more readable component.

Another strength of this technique lies in how easy it makes testing the Customer component. You don't need to worry about testing the business logic; you only need to test the UI logic. Additionally, you can test the custom hooks separately.

As a final highlight, let's see what happens if you decide to add a new custom hook that adds some logic to the Customer component, such as a custom hook that handles logging customer information.

import composeHooks from 'react-hooks-compose';
import { useCustomerHook, useLoggerHook } from './hooks';

const Customer = ({name, surname, age}) => {
  return (
    <div>
      <p>{name}</p>
      <p>{surname}</p>
    </div>
  );
};

export default composeHooks({
  useCustomerHook,
  useLoggerHook
})(Customer);
Enter fullscreen mode Exit fullscreen mode

Perfect, you've added the custom hook useLoggerHook to the Customer component without having to modify the component itself.

This is because useLoggerHook was composed with the useCustomerHook.

Conclusions

In this article, we've explored how to decouple the logic of a React component through hook composition, turning it into a pure presentation component. The art of hook composition provides us with a powerful tool to enhance the modularity and maintainability of our React components.

By clearly separating business logic from presentation, we make our components more readable and easier to test. This methodology promotes code reusability and the scalability of React applications, allowing developers to focus on creating cleaner and more performant components.

If you have any suggestions or questions on this topic, please feel free to share them in the comments below. And if you found this article helpful, don't forget to share it with your fellow developers!

Top comments (20)

Collapse
 
starkraving profile image
Mike Ritchie

This creates a different type of dependency though, right? Each of the hooks have to return an object so that they can be destructured into a final composite object, which means you can’t have a hook that returns an array. And if two of the hooks have the same key then one will get overwritten. Still, it’s a very cool idea and I can see how useful it is. Thanks for the article!

Collapse
 
argonauta profile image
Riccardo Tartaglia

Yes, your observation is right, but using Typescript (not used in this article) can mitigate these "issues".

Thanks for your feedback.

Collapse
 
adeleke5140 profile image
Kehinde Adeleke

Thank you for writing and sharing this article. I have a question though:

  1. Do we need an external lib to compose hooks? In order to reduce bundle size and dependence on external components, can we do this ourself?

  2. While reading through your article, dependency injection kept coming to mind. Is it by any chance related to decoupling business logic from ui components?

Collapse
 
argonauta profile image
Riccardo Tartaglia
  1. You can write a simple reduce function for the hooks execution. Take a look to this: github.com/helloitsjoe/react-hooks...

  2. tl,dr; yes, dependency injection is similar to higher-order functions :)

Collapse
 
xaverb profile image
XaverB

One of the benefits of DI is decoupling. This is made possible by following “program to an interface, not imementation”. Moreover, we follow another good coding practice “dependency inversion principle”.

Great question!

Collapse
 
dvgy profile image
GAURAV YADAV • Edited

If I use useState and useEffect inside <Customer/> will it still be pure component ?

Collapse
 
argonauta profile image
Riccardo Tartaglia • Edited

Yes, if you managed only presentation logic :)

Collapse
 
nick__84343ca2422df81e0ca profile image
Nick

What if hook depend on an value from another hook ?

Collapse
 
argonauta profile image
Riccardo Tartaglia • Edited

Maybe you can compose this two hooks in a new one.

Hooks are "functions" to manage business logic in React, it's ok that one hook depends from one other, it's a problem if an hook depends from five others hooks

Collapse
 
eparrita profile image
Erick Parra

This article is about high order components HOC, the are different way to decoupling the logic like compound components, context API, etc..

Collapse
 
argonauta profile image
Riccardo Tartaglia

In reality it is the High Order Function pattern (HOC was based on the same pattern) applied to the hooks philosophy.

As you say there are many ways for decoupling your components :)

Collapse
 
cantillojo54982 profile image
Joiner

Glad to know about this.
Is this method considering the unneccessary re-render based on component tree structure?
I am not sure about this. Please let me know. Thanks

Collapse
 
argonauta profile image
Riccardo Tartaglia

This method is only a way to separate logic from UI, don't optimize re-renders in the tree structure

Collapse
 
filatovserhii profile image
Sergey Filatov

Thank you for the article. How to do without the library react-hooks-compose library?

Collapse
 
mflorida profile image
Mark M. Florida

The source is pretty short - it wouldn't be terrible to just grab that and integrate it into your project source.

github.com/helloitsjoe/react-hooks...

Collapse
 
teneko profile image
Teneko • Edited

"The source code is pretty short - it wouldn't be terrible to just grab that and integrate it into your project source [with respect to the license.]"*

Please please always look at the license and respect the effort people spent especially for Open Source projects licensed under MIT and similiar. It cannot be more demotivating providing open source code and reading someone writing "just grab the code". :s

And if there is no license, which is the case for react-hooks-compose, then you simply cannot and should not use the source code at all: opensource.stackexchange.com/a/1721.

Collapse
 
argonauta profile image
Riccardo Tartaglia

You can use a simply reduce function for the serial esecution of the hooks

Collapse
 
dreambit profile image
dreambit

what theme is used in the preview?)

Collapse
 
argonauta profile image
Riccardo Tartaglia

I've used ray.so to generate the preview :)

Collapse
 
lum3ll_96 profile image
Luca Mellano

Nice article indeed. Some big problem using Typescript.