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
Form
component. - Inside the
From
we havep
andinput
. - The
input
is the input for the user. - The
p
shows 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
menu
components. - Inside the first component, we have a
button
that 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
App
component reads the twoMenus
we mentioned. - The
Menu
component reads the title and the children (in this case, theButton
). -
Button
has a button element with a click event.**handleClick
is 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
stopPropagation
is the Higher-Order Component. It receives a component (Button
in 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 prop
andHOC
patterns. - 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)