Separating responsibilities in your code (using React Hooks as example)

If you are interested in DDD, architecture and best practices check out my repository: Domain-Driven Hexagon

Sairys
4 min readAug 8, 2019

Today i will share with you my view on how to make your application framework agnostic, clean, scalable and easy testable regardless of what framework you are using by separating responsibilities. In this example I will be using React with hooks.

I define three layers of responsibilities:

  • Presentational components - components that just render HTML and are primarily concerned with how things look.
  • Business logic - logic defined by the business/client (like manipulating data: sorting, calculations etc)
  • Implementation logic - intermediate logic that makes your app work, connects business logic to your presentational components (manipulating the DOM, state changes, network requests etc). This is usually done using frameworks.

Imagine a small component for your shopping cart to select quantity of products you want to buy:

Quantity selector
  • You can increase and decrease quantity of products by pressing + and -
  • Going below 0 or above 10 will give some kind of error message.

You can try out this component below:

Now, lets try to implement it. You can create a component like this:

But there is a problem in this component. It has no separation of concerns. It could be fine for small components, but imagine that your component grows and you need to add more logic here. It can become bloated really fast. It’s harder to maintain and find bugs.

First, we will separate this component in 2 parts:

  1. We will create our own custom React Hook in a separate file:
ustom hook
Custom hook

This is a hook that returns 2 functions for manipulating the state and the state itself. (notice “use” in function name, it is just a convention for naming react hooks)

2. Next, we import this into our component and destructure values:

Presentational component
Presentational component

This is now a proper presentational component since it has no logic inside and is only responsible for visualizing information.

But wait, it’s not all. Let’s make it even better by separating implementation logic (framework logic) from business logic:

(Since this component is so small I am using both business and implementation logic in the same file, but you can easily separate those in multiple files if your component grows.)

Separating business logic outside of our custom hook

First half of the code: what is happening here is that we are separating logic for setting message and increasing/decreasing item quantity(value) into small pure functions. It’s a lot easier to test, refactor and maintain your business logic when it’s not all tangled up with other things like network requests and DOM updates.

Second half of the code: Only Implementation/framework logic is left here. useQuantitySelector() is a custom hook which does only state manipulations.

Things you can do in React Hook:

  • Encapsulate state changes
  • Implement side effects (like dom manipulations, network requests etc) inside useEffect() function (or componentDidMount() in case of class components).
  • Other framework related job

Working with data: filtering, sorting, etc should be done outside of a hook, inside its own small functions (preferably pure functions and functional composition), because testing such functions is very easy.

If you want to add more functionality to your component, like implementing “Add to Cart” button, you can create a new hook (something like useAddToCart() function) using the same principles. Don’t add it to the existing hook. Just create a new one. Pass parameters to a new hook if you need to use some value in it. Always separate responsibilities.

  • With a separation like this, when errors happen it will be much easier to find source of bugs. We now have a better quality, cleaner code.
  • You can reuse custom hooks and business logic in other components.
  • You can reuse your presentational component with a different set of hooks with their own logic.
  • Your app is easier to fix in case if your framework introduces breaking changes with a new update.
  • You can move business logic to another framework if you decide to switch to Vue or Angular for example, since our business logic is framework agnostic now.

Separating your code into business and implementation logic can work in all kinds of applications, like backend or game development, and is called framework agnostic.

This is a small example, but imagine a big component with a lot of logic and it becomes clear why this approach is better than just gluing everything together.

If you want to find out more about advantages this approach offers, I would recommend you to google articles about separation of concerns, framework agnostic applications, pure functions.

Source code on CodeSandbox

Check out my repository: https://github.com/Sairyss?tab=repositories

--

--

Sairys
Sairys

Responses (10)