React Patterns
It's great to see how developers are constantly discovering new patterns in the React ecosystem. Let's focus on some.
Table of Contents:
React has one of the most (don't forget about Python) fast-growing communities in the field of professional web development. And it is quite logical that with such an active growth of developers, a large number of various approaches, writing styles, and patterns appear. It's great to see how developers are constantly discovering new patterns in the React ecosystem. Some kind of pattern that you once considered an anti-pattern suddenly gets a name, and developers start using it. But some well-established models have been used for a long time - let's concentrate on them.
High Order Component
Let's assume that we have the following component:
In order not to be misleading, we define that the get, set, and subscribe methods are just helpers for working with localStorage:
What problem will we face if we take a closer look at the App component?
Firstly, those things that relate to the sidebar should not concern the App - you need to try to keep it as clean as possible.
Secondly, such logic may be required by us not only in the App component, therefore we must endure this logic.
Let's try this with HOC.
There are only three tasks that we must execute:
- Rid the App of logically related to localStorage.
- Create a HOC withStorage, with which the App will get all the necessary data and methods to manage this data.
- Ensure that withStorage is flexible and can be used anywhere in our application.
Let's start!
And we will start with the second paragraph - it will be much easier to follow the changes in our application.
Start writing withStorage. What function should perform withStorage? What useful should he be able to?
WithStorage should take on everything related to working with localStorage. This means that the components that “turn around” in withStorage should receive the data that they request, as well as the methods from localStorage.
Let's try:
Let's take a closer look at what happens inside the repository:
- All that will be passed as the key parameter in withStorage will be in its state.
- Any component that will be in the Comp parameter will get the setStorage method and all the necessary values from localStorage as props.
- When unmounting a component, HOC withStorage will unsubscribe from any changes to localStorage.
This logic allows you to be extremely flexible withStorage, which means that it doesn’t matter exactly which fields from localStorage need to be obtained or changed - we determine this, and withStorage only provides information and tools for its management.
And finally, we will return to the first point - we need to clean the App. Remove some lifecycle hooks, state and change where the App should get data from - in our case, not from the state, but from the props. As well as methods for data management:
I think that the syntax of the call withStorage will not be so unexpected any more - using the example of an App, we only need to slightly change its usual export. Now it will look like this:
It turns out that HOC is a “wrapper” over the component that we have transmitted, which expands its functionality and (or) provides additional data.
Compound Components
Like last time, let's immediately consider the problem by example. We have an App component:
and components RadioGroup and RadioButton:
Now our buttons are not functioning, and our main task is to make them work!
We do not have to change the App component, but let's take a closer look at its render. As we can see, RadioGroup gets a legend (used only to draw a kind of “cap”), defaultValue, and a set of RadioButton components as children.
As you can see from the task, we need to initially define a different data store for RadioGroup and, not least, to somehow forward this data in RadioButton`s. In turn, when clicking on any of the buttons (RadioButton), the data inside the RadioGroup must be changed.
Let's create this data store inside the RadioGroup component:
So, our initialized value will depend on the passed parameter defaultValue
As can be seen from the render of the RadioButton component, isActive is “tough” for us. This needs to be fixed, because by clicking on different buttons, isActive will always change.
It turns out that we should receive isActive from props:
Now you need to make RadioButton get this isActive from RadioGroup. Here, React.Children and React.cloneElement come to the rescue to identify new props.
Do you see what has changed? Instead of the standard this.props.children, we clone each child element, expanding its props. Thus, inside each RadioButton there will be two parameters - value (which was transmitted inside the App), and isActive (true, if the value inside RadioButton is equal to the value from the RadioGroup state).
Now, you need to write a function that will change the value from the state of RadioGroup. When will this function be called? When any of the buttons inside the RadioButton is clicked.
RadioButton does not have its own state, and if inside the component there is some kind of event that changes data that is not inside the state of this component, props is what we need. Let's add RadioButton a little:
and in the same way as isActive, we can pass onSelect to this component:
It seems that we coped with the task!
It turns out that even if the component has children among its props, this does not mean that it should render it. In our case, we were able to clone the children, and render something based on them.
Render Props
The render props pattern is somewhat similar to HOC in its essence — it also serves to gain flexibility and reuse components and their data.
I think you already guessed what we will do now. Not? Consider an example!
As you can see from the App, all that is happening here is working with the current position of the mouse relative to the screen. Any component can use this kind of logic, so I think it would be correct to put this into a separate component. We can do this with HOC, and I think you already know how. But let's try it with the render props pattern.
To begin with, we will move all the logic regarding the position of the mouse into a separate component, for example, Mouse:
Already inside the render method, you can see what the essence of the render props pattern is - we know that the render function will be passed to us as a parameter (it can be called anything - I chose the name to render only by convention), and we determine which arguments will be passed into this function. Depending on where the Mouse component will be rendered, this data can be used in different ways. In the case of our App, it will look like this:
This pattern is widely used in UI-libraries, auxiliary modules, and not only.
Conclusion
Of course, this is not a complete list of patterns that are used in the react community. We will definitely tell you more if this article's helped you write better code. If you're looking for true professionals, consider our staff augmentation services - you will definitely find the developer you need!