|
1 | 1 | ---
|
2 | 2 | date: 2019-01-16T09:04:31.645Z
|
3 |
| -title: 'React Hooks: Making it easier to compose, reuse, and share React code' |
4 |
| -banner: /assets/social-hocks.png |
| 3 | +title: 'One hell of a title' |
5 | 4 | posttype: post
|
6 |
| -category: react |
7 |
| -shape: diamond |
8 |
| -medium: https://medium.com/exodevhub/react-hooks-making-it-easier-to-compose-reuse-and-share-react-code-328f3bb49b16 |
9 |
| -authorLink: Tom Bowden |
10 |
| -author: Tom Bowden |
11 | 5 | ---
|
12 | 6 |
|
13 |
| -> ⚠️ **Warning** |
14 |
| -[React Hooks](https://reactjs.org/docs/hooks-intro.html) are an upcoming feature in React, currently (January 2019) in **alpha** (React v16.7.0-alpha). |
15 |
| - |
16 |
| -Hooks are an upcoming feature in React that enable you to use state and many other React features without writing a class. This has some important ramifications for the future of React code, especially with regard to how components will be composed. |
17 |
| - |
18 |
| -The motivation for hooks, as provided by the [official documentation](https://reactjs.org/docs/hooks-intro.html) from the Facebook React team, is that hooks solve some problems that they have encountered over five years of writing and maintaining React components. These problems are: |
19 |
| - |
20 |
| -1. It’s hard to reuse stateful logic between components |
21 |
| -2. Complex components become hard to understand |
22 |
| -3. Classes confuse both people and machines |
23 |
| - |
24 |
| -In this short article, we will focus on how React hooks solve the first problem — the difficulty of reusing stateful logic between components — because it has the most wide-ranging consequences. |
25 |
| - |
26 |
| -## Reusing Stateful Logic |
27 |
| - |
28 |
| -For the past few years, the preferred ways of sharing stateful logic in React are higher-order components (HOCs) and render props. Both HOCs and render props require an additional component in the application component tree, and arguably they also make it somewhat more difficult to reason about the shared logic within the code. Now we can add React hooks as a way of sharing logic. |
29 |
| - |
30 |
| -Let’s compare the options for dealing with cross-cutting concerns in React using a very simple example to highlight the differences between them. |
31 |
| - |
32 |
| -## Higher-Order Component |
33 |
| - |
34 |
| -A [higher-order component](https://reactjs.org/docs/higher-order-components.html) (HOC) is a widely-used pattern in React to reuse component logic, by wrapping the component around a target component and passing data to it via its props. In other words, a higher-order component is a function that takes your target component as an argument, and returns a the target component with additional data and functionality. |
35 |
| - |
36 |
| -The following simple example shows a higher-order component that tracks the mouse position in a web app. |
37 |
| -```js |
38 |
| -function withMousePosition(WrappedComponent) { |
39 |
| - return class extends Component { |
40 |
| - constructor(props) { |
41 |
| - super(props); |
42 |
| - this.state = { x: 0, y: 0 }; |
43 |
| - } |
44 |
| - |
45 |
| - componentDidMount() { |
46 |
| - window.addEventListener("mousemove", this.handleMouseMove); |
47 |
| - } |
48 |
| - |
49 |
| - componentWillUnmount() { |
50 |
| - window.removeEventListener("mousemove", this.handleMouseMove); |
51 |
| - } |
52 |
| - |
53 |
| - handleMouseMove = event => { |
54 |
| - this.setState({ |
55 |
| - x: event.clientX, |
56 |
| - y: event.clientY |
57 |
| - }); |
58 |
| - }; |
59 |
| - |
60 |
| - render() { |
61 |
| - return ( |
62 |
| - <WrappedComponent |
63 |
| - {...this.props} |
64 |
| - mousePosition={this.state} |
65 |
| - /> |
66 |
| - ); |
67 |
| - } |
68 |
| - }; |
69 |
| -} |
70 |
| -``` |
71 |
| -In the wrapped class component above, the mouse position is obtained via the [mousemove event API](https://developer.mozilla.org/en-US/docs/Web/Events/mousemove) provided by browser windows. We set up an event listener and update the state which holds the mouse position coordinates. The class encapsulates the functionality, and now we can share it with other components. |
72 |
| - |
73 |
| -So, using the higher-order component pattern, the function `withMousePosition` takes any target component as an argument, and returns it with all its existing props plus one additional prop: the `mousePosition` coordinates. |
74 |
| -```js |
75 |
| - function App(props) { |
76 |
| - const { x, y } = props.mousePosition; |
77 |
| - |
78 |
| - return ( |
79 |
| - <div className="App"> |
80 |
| - <h1>Higher-Order Component Method</h1> |
81 |
| - <h2>Move the mouse around!</h2> |
82 |
| - <p style={{ background: "orange" }}> |
83 |
| - The current mouse position is ({x}, {y}) |
84 |
| - </p> |
85 |
| - </div> |
86 |
| - ); |
87 |
| - } |
88 |
| - |
89 |
| - const AppWithMousePosition = withMousePosition(App); |
90 |
| -``` |
91 |
| -In this example we have shared the `mousePosition` coordinate data with a presentational `App` component. The dynamic mouse position is displayed in an orange paragraph: |
92 |
| -```js |
93 |
| - <p style={{ background: "orange" }}> |
94 |
| - The current mouse position is ({x}, {y}) |
95 |
| - </p> |
96 |
| -``` |
97 |
| -The wrapped `AppWithMousePosition` component can then be rendered to the `DOM`: |
98 |
| -```js |
99 |
| - ReactDOM.render(<AppWithMousePosition />, document.getElementById("root")); |
100 |
| -``` |
101 |
| -Try the HOC approach for yourself in the following [CodeSandbox](https://codesandbox.io/s/43z216n6y9): |
102 |
| - |
103 |
| -[https://codesandbox.io/s/43z216n6y9](https://codesandbox.io/s/43z216n6y9) |
104 |
| - |
105 |
| -## Render Props |
106 |
| - |
107 |
| -A [render prop](https://reactjs.org/docs/render-props.html) is a way of sharing code between React components using a prop whose value is a function. The prop is often called `render`, thus the terminology “render prop”. |
108 |
| - |
109 |
| -Let’s see how our mouse position example introduced earlier looks when implemented using a render prop: |
110 |
| -```js |
111 |
| - class MousePosition extends Component { |
112 |
| - constructor(props) { |
113 |
| - super(props); |
114 |
| - this.state = { x: 0, y: 0 }; |
115 |
| - } |
116 |
| - |
117 |
| - componentDidMount() { |
118 |
| - window.addEventListener("mousemove", this.handleMouseMove); |
119 |
| - } |
120 |
| - |
121 |
| - componentWillUnmount() { |
122 |
| - window.removeEventListener("mousemove", this.handleMouseMove); |
123 |
| - } |
124 |
| - |
125 |
| - handleMouseMove = event => { |
126 |
| - this.setState({ |
127 |
| - x: event.clientX, |
128 |
| - y: event.clientY |
129 |
| - }); |
130 |
| - }; |
131 |
| - |
132 |
| - render() { |
133 |
| - return ( |
134 |
| - <div |
135 |
| - style={{ height: "100%", width: "100%" }} |
136 |
| - onMouseMove={this.handleMouseMove} |
137 |
| - > |
138 |
| - {this.props.render(this.state)} |
139 |
| - </div> |
140 |
| - ); |
141 |
| - } |
142 |
| - } |
143 |
| -``` |
144 |
| -The stateful logic for the mouse position is the same as we used in the higher-order component earlier. |
145 |
| - |
146 |
| -The difference between the HOC method and this render props method is that we now specify a function prop called `render` within the render method of the class component, which takes the state of the component as an argument, and renders it as a child of the class component: |
147 |
| -```js |
148 |
| - render() { |
149 |
| - return ( |
150 |
| - <div |
151 |
| - style={{ height: "100%", width: "100%" }} |
152 |
| - onMouseMove={this.handleMouseMove} |
153 |
| - > |
154 |
| - {this.props.render(this.state)} |
155 |
| - </div> |
156 |
| - ); |
157 |
| - } |
158 |
| -``` |
159 |
| -Note that the terminology “function as child” is also used when referring to this pattern. |
160 |
| - |
161 |
| -Now, we can wrap any target component with this `MousePosition` component, and dynamically render the mouse position by passing it in via the `render` prop. This is a dynamic way of sharing stateful logic, compared with the statically defined higher-order component. |
162 |
| - |
163 |
| -> As an aside, for more details regarding the pros and cons of higher-order components and render props, please see [this excellent article](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce) by Michael Jackson, the co-author of React Router. |
164 |
| -
|
165 |
| -Returning to our example, we can now render a presentational `App` component by composing the `MousePosition` component within it. We render the dynamic mouse position within a sky-blue `<p>` element, which is passed via a function in the `render` prop: |
166 |
| -```js |
167 |
| - function App() { |
168 |
| - return ( |
169 |
| - <div className="App"> |
170 |
| - <h1>Render Props Method</h1> |
171 |
| - <h2>Move the mouse around!</h2> |
172 |
| - <MousePosition |
173 |
| - render={mousePosition => ( |
174 |
| - <p style={{ background: "skyblue" }}> |
175 |
| - The current mouse position is ({mousePosition.x}, {mousePosition.y}) |
176 |
| - </p> |
177 |
| - )} |
178 |
| - /> |
179 |
| - </div> |
180 |
| - ); |
181 |
| - } |
182 |
| -``` |
183 |
| -To summarize, the behavior associated with listening for `mousemove` events and storing the mouse position coordinates has been encapsulated in the `MousePosition` component, and can be used flexibly in any other component, via this “render props” pattern. This is an example of a composable component that has reusable, shareable state logic. |
184 |
| - |
185 |
| -Try the render props approach for yourself in the following [CodeSandbox](https://codesandbox.io/s/rjprzkj29p): |
186 |
| - |
187 |
| -[https://codesandbox.io/s/rjprzkj29p](https://codesandbox.io/s/rjprzkj29p) |
188 |
| - |
189 |
| -## React Hooks |
190 |
| - |
191 |
| -Now, let’s look at how “hooks” could be used to achieve the goal of reusing stateful logic within your apps, using the very same mouse position example: |
192 |
| -```js |
193 |
| - function useMousePosition() { |
194 |
| - const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); |
195 |
| - |
196 |
| - function handleMouseMove(event) { |
197 |
| - setMousePosition({ |
198 |
| - x: event.clientX, |
199 |
| - y: event.clientY |
200 |
| - }); |
201 |
| - } |
202 |
| - |
203 |
| - useEffect(() => { |
204 |
| - window.addEventListener("mousemove", handleMouseMove); |
205 |
| - |
206 |
| - return () => { |
207 |
| - window.removeEventListener("mousemove", handleMouseMove); |
208 |
| - }; |
209 |
| - }, []); |
210 |
| - |
211 |
| - return mousePosition; |
212 |
| - } |
213 |
| -``` |
214 |
| -Note that we have created a “[custom hook](https://reactjs.org/docs/hooks-custom.html)” here called `useMousePosition`. It is a function component, not a class component, but it does encapsulate state! |
215 |
| - |
216 |
| -For our mouse position example, we are using two different React hooks within the body of our custom hook function: |
217 |
| - |
218 |
| -- [State hook](https://reactjs.org/docs/hooks-state.html): `useState` |
219 |
| -- [Effect hook](https://reactjs.org/docs/hooks-effect.html): `useEffect` |
220 |
| - |
221 |
| -The `useState` hook lets us add React state to function components, without having to convert them into class components. The `useState` function hook takes the initial value of state as an argument, and returns a two-element array containing the state value (`mousePosition`), and a function to update that value (`setMousePosition`). You can see at the bottom of the function that we are returning the `mousePosition` state value from the function. |
222 |
| - |
223 |
| -The `useEffect` hook lets you perform side effects in function components. Examples of side effects are getting data from an API, listening for browser events, and manually changing the DOM. The `useEffect` hook carries out the same tasks as the lifecycle methods `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` combined do in class components. |
224 |
| - |
225 |
| -`useEffect` takes a callback function (called the “effect”) as its first argument, and runs it after each render of the component. In our example, the effect is to set up the `mousemove` event listener after the first render when the component is mounted. The returned callback from the effect, if specified, serves to “clean up” before the component is unmounted. In our example, we are removing the event listener when we unmount. |
226 |
| -```js |
227 |
| - useEffect(() => { |
228 |
| - window.addEventListener("mousemove", handleMouseMove); |
229 |
| - |
230 |
| - return () => { |
231 |
| - window.removeEventListener("mousemove", handleMouseMove); |
232 |
| - }; |
233 |
| - }, []); |
234 |
| -``` |
235 |
| -Within the effect callback, we are setting up a `mousemove` event listener called `handleMouseMove`, which itself calls `setMousePosition` with the updated mouse coordinates whenever the user moves the mouse. |
236 |
| - |
237 |
| -The second argument to the `useEffect` function hook, if specified, is an **array of specific state values** that the effect will run on whenever the value updates. That is, the effect will run on each re-render of the component triggered by updates to those specific state values. If **no array** is specified, then the default behavior is to re-render the component and fire the effect whenever any of the state values updates. |
238 |
| - |
239 |
| -In our example, we are passing an **empty array** `[]`, which means that the effect does not depend on any state value updating in our component, i.e. our effect only runs on mount and it will clean up on unmount, but it won’t run on any `mousePosition` updates. The event listener already updates the `mousePosition`, so it is unnecessary to re-render the component when that happens. |
240 |
| - |
241 |
| -Our `useMousePosition` custom hook completely replicates the behavior of the class components used in the HOC and render-props patterns earlier. It fully encapsulates the behavior we need in a **very compact**, **easy-to-understand**, and **reusable** way. |
242 |
| - |
243 |
| -Now, we can share this dynamic mouse position functionality in any other component. Let’s call our custom hook `useMousePosition` in our presentational `App` component: |
244 |
| -```js |
245 |
| - function App() { |
246 |
| - const { x, y } = useMousePosition(); |
247 |
| - |
248 |
| - return ( |
249 |
| - <div className="App"> |
250 |
| - <h1>React Hook Method</h1> |
251 |
| - <h2>Move the mouse around!</h2> |
252 |
| - <p style={{ background: "palegreen" }}> |
253 |
| - The current mouse position is ({x}, {y}) |
254 |
| - </p> |
255 |
| - </div> |
256 |
| - ); |
257 |
| - } |
258 |
| -``` |
259 |
| -Here, we are rendering the dynamic mouse coordinates in a pale green `<p>` tag. |
260 |
| - |
261 |
| -Try the hooks approach for yourself in a [CodeSandbox](https://codesandbox.io/s/548z479m0n): |
262 |
| - |
263 |
| -<iframe src="https://codesandbox.io/embed/548z479m0n" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe> |
264 |
| - |
265 |
| -## Summary |
266 |
| - |
267 |
| -Now you’ve seen the same mouse position example implemented in three different ways: **higher-order components**, **render props**, and **hooks**. |
268 |
| - |
269 |
| -It is clear that by far and away **the most elegant** and **easy to follow** code is found in the React hook approach. In addition, **less code** is needed to achieve **the same results**. |
270 |
| - |
271 |
| -Hooks make it easier than ever to separate out stateful component logic, data, and functionality into an encapsulated structure, making it convenient to reuse and share. The implications of this should not be underestimated. This is a **huge and exciting** development for React and everyone who uses it! |
| 7 | +Wow this content is amazing |
0 commit comments