This article originally appeared at bugfender.com: React Design Patterns (Part 1).
Having studied React for several months, one of the subjects I've paid particularly close attention to is design patterns. In this article, I'll share my key findings.
Note: Some of the patterns focus on state management concepts, but we can avoid Redux, Mobx and other third-party state management tools because they're not related to the subject of this article.
Render Props
Abounding to React docs:
The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.
In simple words, it's just a prop with a function value. The function is a component that needs to be rendered. Maybe you've seen it in React Router:
<Route
path='/about'
render={(props) => (
<About {...props} isLoad={true} />
)}
/>
The primary purpose of this pattern is to update props of sibling components. It makes the components more reusable and helps us to implement the "separation of concerns" more easily.
Let's take the following scenario as an example:
- We need to develop a
Formcomponent. - Inside the
Fromwe havepandinput. - The
inputis the input for the user. - The
pshows what the user writes.
We can simply create something like this:
import React, { useState } from "react";
export default function Input(props) {
return (
<>
<input
type="text"
value={props.value}
onChange={props.onChange}
/>
</>
);
}
export default function Form() {
const [value, setValue] = useState("");
return (
<form>
<Input onChange={e => setValue(e.target.value)}/>
<p>{value}</p>
</form>
);
}
There are two issues with this approach:
1. We don't use the "septate of concern" concept in this case because the Input should control the Value and not the Form.
2. Our components are not so reusable and flexible.
We can refactor the code and use Render Props like this:
import React, { useState } from "react";
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{props.render && props.render(value)}
</>
);
}
export default function Form() {
return (
<form>
<Input render={(value) => <p>{value}</p>} />
</form>
);
}
In this way the Input component controls the value, and it is much more reusable (the same functionality can be implemented with different elements).
HOC - Higher-Order Components
Higher-Order Components are basically a function that receive a component as an argument and return a new component with specific business logic inside. You maybe saw this in 'Redux':
export default connect(mapStateToProps , mapDispatchToProps)(From);
With Higher-Order Components, you can write a separate functionality to your app's commons (global) functions and reuse it on diffident components in your project.
Let's take another scenario:
- We need to develop two
menucomponents. - Inside the first component, we have a
buttonthat needs to block the menu click event. - The second component is also a
button, but this time we need to work with the menu click event.
The problem is that we need two kinds of menus - one with stopPropagation ability and the second without it.
We can use Higher-Order Components like this:
import React from "react";
import "./style.css";
function stopPropagation(WrappedComponent) {
return function(){
const handleClick = event => {
event.stopPropagation();
WrappedComponent.handleClick()
};
return <WrappedComponent onClick={handleClick} />;
}
}
function Button(props){
const handleClick = () => console.log("button clicked!");
Button.handleClick = handleClick;
return <button onClick={props.onClick || handleClick}>Click Me</button>;
}
function Menu(props) {
const openMenu = () => console.log("menu opened!");
return (
<div onClick={openMenu} className="menu">
<h1>Menu</h1>
{props.children}
</div>
);
}
export default function App() {
const ButtonPropagation = stopPropagation(Button);
return (
<div>
<Menu>
<ButtonPropagation />
</Menu>
<Menu>
<Button />
</Menu>
</div>
);
}
Let's analyze this code:
- The
Appcomponent reads the twoMenuswe mentioned. - The
Menucomponent reads the title and the children (in this case, theButton). -
Buttonhas a button element with a click event.**handleClickis the basic functionality of the event.** We need to export this function usingButton.handleClick= handleClick(in the class component you can do it withstatic). -
The
stopPropagationis the Higher-Order Component. It receives a component (Buttonin our case) and sends back the component with new ability (in our casestopPropagation).
This is a simple example of the use of Higher-Order Components. We can use stopPropagation and don't need to rewrite again on different components. Even more importantly, we can create other "button" HOCs like preventDefault and queueClick.
Ok, that's all for part one of the article. In the second part, I will discuss the Context pattern, thePresentational and Container Components pattern and the compound components pattern.
Thank you for reading. I hope you enjoyed the tutorial and learned something new. If you have something to add, please leave a comment. And if you would like more information, here are some excellent resources on the subject:
-
Master React Design Patterns 🔥 (render prop & HOC) - In this video, you will find an explanation of
render propandHOCpatterns. - React Context & Hooks Tutorial - It's a playlist of 21 videos that explain React Context and React Context in hooks (there are some videos that explain hooks in general - you can skip them if you know hooks).
- React Design Patterns: Presentational and Container Components - This video explains what Presentational and Container Components (known also as "smart and dumb components") are.
- Building Flexible Components with React Hooks - An excellent blog post that explains the compound components pattern. If you want an even more advanced resource about the subject, you can watch this video React Hooks: Refactor compound components to hooks.

Top comments (0)