|
| 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