In this post I want to show a way to use Higher-Order Components with React Hooks in order to structure complex components. This method can be described as an architectural pattern to give hooks a straight forward way to use them in order to prevent developers from falling for the traps hooks do have.
This article is for you if you had one of the following thoughts 🤔:
- My components become quite large and hard to maintain.
- I don’t know where to find some logic which handles my component’s behavior.
- How do I test a complex component and can be sure it works?
A couple of days ago I came across this article from Albert Chu describing the advantages of Higher-Order Components (HOCs) and how they are useful in a project with React Hooks. Albert describes the use cases for HOCs and outlines how you can use them to structure your components.
I want to add some more thought to this.
Use-Cases for Higher-Order Components
The main reason for me to use Higher-Order Components is that they help to separate the business logic or controlling logic from the presentation. This pattern enforces Separation of Concerns and helps to implement the Single Responsibility Principle.
A lot of advocacy around the topic of hooks is as follows: you can use state within your Function-Component, so you don't need to use a HOC for that. Same goes for life-cycle logic, hooks based on
useEffect and others.
Lately I have seen a lot of developers do exactly this: They put their local state into the Presentation Component and any life-cycle method is handled within this one component.
For a long time I was on the same track. But working more and more with hooks I came to the conclusion: This is not a good idea in most cases.
The disadvantage to hooks is that if you use them incorrectly, you could spread logic that could be self-contained in one place across all the components in your app, opening up lots of opportunities to forget things or to spread duplicated bugs into a thousand places in your app instead of just one.
– From Eric Elliott, Do React Hooks Replace Higher Order Components (HOCs)?
An additional thought to Eric's words is that you mix up your logic of the component with the presentation itself. With this it becomes more complicated to understand and to test components. Especially if your components hold a lot of logic.
So here is a proposal to use Higher-Order Components to tackle these problems:
Using HOCs to structure Complex Components
Using Higher-Order Components to decouple logic from pure presentation of a component is quite simple. Just remove all the code that is related to handling state and the life-cycle of the component and put it into a separate component, the HOC.
Let's look at a minimalistic example.
This component contains some state and life-cycle behavior.
Now we can separte out both the state and the life-cycle behavior into a Higher-Order Component.
Apart from that it is possible to use Custom Hooks for the life-cycle behavior itself which represents the logic of the component.
The React Team advocates the usage of Custom Hooks if you want to share functionality between components:
– From Building Your Own Hooks
In my opinion it is also a valid use case for modularizing code since it helps to decouple logic and helps with the testabilty of your code.
Let’s look at the files:
As you can see, the code is more spread out into separate distinct components which supports the Separation of Concerns approach of writing code.
You can now test Custom Hooks integrated in real components using react-hooks-testing-library 🐏 which is fantastic.
In my experience testing hooks is complex. If you use Enzyme you need to
mount components to make hooks testable. And even then it is still hard to test all branches of a hook. Adam Witalewski writes about how you can test hooks with Enzyme.
To tackle this problem here is a proposal for writing a Custom Hook:
This is basically the architecture of what I call Conceptual Components.
Conceptual Components describe the way of how you structure a component in order to make the complexity of the component’s logic and presentation easier to understand.
Conceptual Components focus on writing code that is easy to understand for developers.
When to use Conceptual Components
These are the things you should consider before putting state or life-cycle behavior into a Presentation Component:
- Is there logic in this component? If yes,
- Is the component’s logic bound in one place?
- Can you reuse the logic in different places where it is needed?
- Is the component easily and completely testable?
When you come to the conclusion it is viable for you to put state into the Presentation Component itself, do so. In any other case use Conceptual Components to prevent mixing logic and presentation.
Summary of the advantages
- Clear separation of concerns for your code
- "Easy to test"-API of the Presentation Component
- Unit testing for Hooks becomes easy
The Downside of using Conceptual Components
Sure, using this architectural pattern does not come for free.
Writing HOCs that encapsulate the component’s logic brings extra code into your project. And with this comes another problem: You need to write two separate React Components which pollutes your Component Tree. This can become a positive feature though when debugging since error stacks become more precise.
I think it's worth the additional effort in favor of the advantages the pattern of Conceptual Components brings with it.
Lately I have used the concept of Conceptual Components in my daily work. It makes reading code easy for me and removes the cluttered components that include a lot of code.
I am interested in your view on this topic. How do you solve the problem of complex components that hold a lot of code? What do you think of Conceptual Components?
Thanks to Peter Kröner for providing feedback on this post.