Skip to content

Commit 605c928

Browse files
committed
Add hooks overview, useState and useEffect docs
1 parent b538e55 commit 605c928

File tree

4 files changed

+582
-0
lines changed

4 files changed

+582
-0
lines changed
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
---
2+
title: useEffect Hook
3+
description: "Details about the useEffect React hook in ReScript"
4+
canonical: "/docs/react/latest/hooks-effect"
5+
category: "Hooks & State Management"
6+
---
7+
8+
# useEffect
9+
10+
<Intro>
11+
12+
The *Effect* Hook lets you perform side effects in function components.
13+
14+
</Intro>
15+
16+
## What are Effects?
17+
18+
Common examples for (side) effects are data fetching, setting up a subscription, and manually changing the DOM in React components.
19+
20+
There are two common kinds of side effects in React components: those that don’t require cleanup, and those that do. We'll look into the distinction later on in our examples, but first let's see how the interface looks like.
21+
22+
23+
## Basic Usage
24+
25+
<CodeTab labels={["ReScript", "JS Output"]}>
26+
27+
```res
28+
// Runs after every completed render
29+
React.useEffect(() => {
30+
// Run effects
31+
None // or Some(() => {})
32+
})
33+
34+
35+
// Runs only once right after mounting the component
36+
React.useEffect0(() => {
37+
// Run effects
38+
None // or Some(() => {})
39+
})
40+
41+
// Runs everytime `prop1` has changed
42+
React.useEffect1(() => {
43+
// Run effects based on prop1
44+
None
45+
}, [prop1])
46+
47+
// Runs everytime `prop1` or `prop2` has changed
48+
React.useEffect2(() => {
49+
// Run effects based on prop1 / prop2
50+
None
51+
}, (prop1, prop2))
52+
53+
React.useEffect3(() => {
54+
None
55+
}, (prop1, prop2, prop3));
56+
57+
// useEffect4...7 with according dependency
58+
// tuple just like useEffect3
59+
60+
```
61+
62+
```js
63+
React.useEffect(function () { });
64+
React.useEffect((function () { }), []);
65+
React.useEffect((function () { }), [prop1]);
66+
React.useEffect((function () { }), [ prop1, prop2 ]);
67+
React.useEffect((function () { }), [ prop1, prop2, prop3 ]);
68+
```
69+
70+
</CodeTab>
71+
72+
`React.useEffect` receives a function that contains imperative, possibly effectful code, and returns a value `option(unit => unit)` as a potential cleanup function.
73+
74+
A `useEffect` call may receive an additional array of dependencies (see `React.useEffect1` / `React.useEffect2...7`). The effect function will run whenever one of the provided dependencies has changed. More details on why this is useful [down below](#effect-dependencies).
75+
76+
**Note:** You probably wonder why `React.useEffect1` receives an `array`, and `useEffect2` etc require a `tuple` (e.g. `(prop1, prop2)`) for the dependency list. That's because a tuple can receive multiple values of different types, whereas an `array` only accepts values of identical types. It's possible to replicate `useEffect2` by doing `React.useEffect1(fn, [1, 2])`, on other hand the type checker wouldn't allow `React.useEffect1(fn, [1, "two"])`.
77+
78+
`React.useEffect` will run its function after every completed render, while `React.useEffect0` will only run the effect on the first render (when the component has mounted).
79+
80+
81+
## Examples
82+
83+
### Effects without Cleanup
84+
85+
Sometimes, we want to run some additional code after React has updated the DOM. Network requests, manual DOM mutations, and logging are common examples of effects that don’t require a cleanup. We say that because we can run them and immediately forget about them.
86+
87+
As an example, let's write a counter component that updates `document.title` on every render:
88+
89+
<CodeTab labels={["ReScript", "JS Output"]}>
90+
91+
```res
92+
// Counter.re
93+
module Document = {
94+
type t;
95+
@bs.val external document: t = "document";
96+
@bs.set external setTitle: (t, string) => unit = "title"
97+
}
98+
99+
@react.component
100+
let make = () => {
101+
let (count, setCount) = React.useState(_ => 0);
102+
103+
React.useEffect(() => {
104+
open Document
105+
document->setTitle(`You clicked ${Belt.Int.toString(count)} times!`)
106+
None
107+
}, );
108+
109+
let onClick = (_evt) => {
110+
setCount(prev => prev + 1)
111+
};
112+
113+
let msg = "You clicked" ++ Belt.Int.toString(count) ++ "times"
114+
115+
<div>
116+
<p>{React.string(msg)}</p>
117+
<button onClick> {React.string("Click me")} </button>
118+
</div>
119+
}
120+
```
121+
```js
122+
function Counter(Props) {
123+
var match = React.useState(function () {
124+
return 0;
125+
});
126+
var setCount = match[1];
127+
var count = match[0];
128+
React.useEffect(function () {
129+
document.title = "You clicked " + String(count) + " times!";
130+
131+
});
132+
var onClick = function (_evt) {
133+
return Curry._1(setCount, (function (prev) {
134+
return prev + 1 | 0;
135+
}));
136+
};
137+
var msg = "You clicked" + String(count) + "times";
138+
return React.createElement("div", undefined,
139+
React.createElement("p", undefined, msg),
140+
React.createElement("button", {
141+
onClick: onClick
142+
}, "Click me"));
143+
}
144+
```
145+
146+
</CodeTab>
147+
148+
In case we want to make the effects dependent on `count`, we can just use following `useEffect` call instead:
149+
150+
```res
151+
React.useEffect1(() => {
152+
open Document
153+
document->setTitle(`You clicked ${Belt.Int.toString(count)} times!`)
154+
None
155+
}, [count]);
156+
```
157+
158+
Now instead of running an effect on every render, it will only run when `count` has a different value than in the render before.
159+
160+
### Effects with Cleanup
161+
162+
Earlier, we looked at how to express side effects that don’t require any cleanup. However, some effects do. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don’t introduce a memory leak!
163+
164+
Let's look at an example that gracefully subscribes, and later on unsubscribes from some subscription API:
165+
166+
<CodeTab labels={["ReScript", "JS Output"]}>
167+
168+
```res
169+
// FriendStatus.res
170+
171+
module ChatAPI = {
172+
// Imaginary globally available ChatAPI for demo purposes
173+
type status = { isOnline: bool };
174+
@bs.val external subscribeToFriendStatus: (string, status => unit) => unit = "subscribeToFriendStatus";
175+
@bs.val external unsubscribeFromFriendStatus: (string, status => unit) => unit = "unsubscribeFromFriendStatus";
176+
}
177+
178+
type state = Offline | Loading | Online;
179+
180+
@react.component
181+
let make = (~friendId: string) => {
182+
let (state, setState) = React.useState(_ => Offline)
183+
184+
React.useEffect(() => {
185+
let handleStatusChange = (status) => {
186+
setState(_ => {
187+
status.ChatAPI.isOnline ? Online : Offline
188+
})
189+
}
190+
191+
ChatAPI.subscribeToFriendStatus(friendId, handleStatusChange);
192+
setState(_ => Loading);
193+
194+
let cleanup = () => {
195+
ChatAPI.unsubscribeFromFriendStatus(friendId, handleStatusChange)
196+
}
197+
198+
Some(cleanup)
199+
})
200+
201+
let text = switch(state) {
202+
| Offline => friendId ++ " is offline"
203+
| Online => friendId ++ " is online"
204+
| Loading => "loading..."
205+
}
206+
207+
<div>
208+
{React.string(text)}
209+
</div>
210+
}
211+
```
212+
```js
213+
function FriendStatus(Props) {
214+
var friendId = Props.friendId;
215+
var match = React.useState(function () {
216+
return /* Offline */0;
217+
});
218+
var setState = match[1];
219+
React.useEffect(function () {
220+
var handleStatusChange = function (status) {
221+
return Curry._1(setState, (function (param) {
222+
if (status.isOnline) {
223+
return /* Online */2;
224+
} else {
225+
return /* Offline */0;
226+
}
227+
}));
228+
};
229+
subscribeToFriendStatus(friendId, handleStatusChange);
230+
Curry._1(setState, (function (param) {
231+
return /* Loading */1;
232+
}));
233+
return (function (param) {
234+
unsubscribeFromFriendStatus(friendId, handleStatusChange);
235+
236+
});
237+
});
238+
var text;
239+
switch (match[0]) {
240+
case /* Offline */0 :
241+
text = friendId + " is offline";
242+
break;
243+
case /* Loading */1 :
244+
text = "loading...";
245+
break;
246+
case /* Online */2 :
247+
text = friendId + " is online";
248+
break;
249+
250+
}
251+
return React.createElement("div", undefined, text);
252+
}
253+
```
254+
255+
</CodeTab>
256+
257+
258+
## Effect Dependencies
259+
260+
In some cases, cleaning up or applying the effect after every render might create a performance problem. Let's look at a concrete example to see what `useEffect` does:
261+
262+
```res
263+
// from a previous example above
264+
React.useEffect1(() => {
265+
open Document
266+
document->setTitle(`You clicked ${Belt.Int.toString(count)} times!`)
267+
None;
268+
}, [count]);
269+
```
270+
Here, we pass `[count]` to `useEffect1` as a dependency. What does this mean? If the `count` is 5, and then our component re-renders with count still equal to 5, React will compare `[5]` from the previous render and `[5]` from the next render. Because all items within the array are the same (5 === 5), React would skip the effect. That’s our optimization.
271+
272+
When we render with count updated to 6, React will compare the items in the `[5]` array from the previous render to items in the `[6]` array from the next render. This time, React will re-apply the effect because `5 !== 6`. If there are multiple items in the array, React will re-run the effect even if just one of them is different.
273+
274+
This also works for effects that have a cleanup phase:
275+
276+
```res
277+
// from a previous example above
278+
React.useEffect1(() => {
279+
let handleStatusChange = (status) => {
280+
setState(_ => {
281+
status.ChatAPI.isOnline ? Online : Offline
282+
})
283+
}
284+
285+
ChatAPI.subscribeToFriendStatus(friendId, handleStatusChange);
286+
setState(_ => Loading);
287+
288+
let cleanup = () => {
289+
ChatAPI.unsubscribeFromFriendStatus(friendId, handleStatusChange)
290+
}
291+
292+
Some(cleanup)
293+
}, [friendId]) // Only re-subscribe if friendId changes
294+
```
295+
296+
**Important:** If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders
297+
298+
If you want to run an effect and clean it up only once (on mount and unmount), use `React.useEffect0`. This is essentially like comparing `[] === []`.
299+
300+
If you are interested in more performance optmization related topics, have a look at the ReactJS [Performance Optimization Docs](https://reactjs.org/docs/hooks-faq.html#performance-optimizations) for more detailed infos.
301+

0 commit comments

Comments
 (0)