Skip to content

Commit 79b234c

Browse files
authored
Clarify cleanup function usage in Effects
Updated the explanation of cleanup functions in Effects to include 'isCurrent' checks and clarified the handling of fetch requests.
1 parent 2da4f7f commit 79b234c

File tree

1 file changed

+12
-12
lines changed

1 file changed

+12
-12
lines changed

src/content/learn/synchronizing-with-effects.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ To write an Effect, follow these three steps:
4747

4848
1. **Declare an Effect.** By default, your Effect will run after every [commit](/learn/render-and-commit).
4949
2. **Specify the Effect dependencies.** Most Effects should only re-run *when needed* rather than after every render. For example, a fade-in animation should only trigger when a component appears. Connecting and disconnecting to a chat room should only happen when the component appears and disappears, or when the chat room changes. You will learn how to control this by specifying *dependencies.*
50-
3. **Add cleanup if needed.** Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, "connect" needs "disconnect", "subscribe" needs "unsubscribe", and "fetch" needs either "cancel" or "ignore". You will learn how to do this by returning a *cleanup function*.
50+
3. **Add cleanup if needed.** Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, "connect" needs "disconnect", "subscribe" needs "unsubscribe", and "fetch" needs "cancel", "ignore", or an "isCurrent" check. You will learn how to do this by returning a *cleanup function*.
5151

5252
Let's look at each of these steps in detail.
5353

@@ -684,30 +684,30 @@ In development, opacity will be set to `1`, then to `0`, and then to `1` again.
684684
685685
### Fetching data {/*fetching-data*/}
686686
687-
If your Effect fetches something, the cleanup function should either [abort the fetch](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) or ignore its result:
687+
If your Effect fetches something, the cleanup function should either [abort the fetch](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) or ignore its result if it is not the most current rendering:
688688
689689
```js {2,6,13-15}
690690
useEffect(() => {
691-
let ignore = false;
691+
let isCurrent = true;
692692

693693
async function startFetching() {
694694
const json = await fetchTodos(userId);
695-
if (!ignore) {
695+
if (isCurrent) {
696696
setTodos(json);
697697
}
698698
}
699699

700700
startFetching();
701701

702702
return () => {
703-
ignore = true;
703+
isCurrent = false;
704704
};
705705
}, [userId]);
706706
```
707707
708708
You can't "undo" a network request that already happened, but your cleanup function should ensure that the fetch that's _not relevant anymore_ does not keep affecting your application. If the `userId` changes from `'Alice'` to `'Bob'`, cleanup ensures that the `'Alice'` response is ignored even if it arrives after `'Bob'`.
709709
710-
**In development, you will see two fetches in the Network tab.** There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned up so its copy of the `ignore` variable will be set to `true`. So even though there is an extra request, it won't affect the state thanks to the `if (!ignore)` check.
710+
**In development, you will see two fetches in the Network tab.** There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned up so its copy of the `isCurrent` variable will be set to `false`. So even though there is an extra request, it won't affect the state thanks to the `if (isCurrent)` check.
711711
712712
**In production, there will only be one request.** If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
713713
@@ -1551,15 +1551,15 @@ export default function Page() {
15511551
const [person, setPerson] = useState('Alice');
15521552
const [bio, setBio] = useState(null);
15531553
useEffect(() => {
1554-
let ignore = false;
1554+
let isCurrent = true;
15551555
setBio(null);
15561556
fetchBio(person).then(result => {
1557-
if (!ignore) {
1557+
if (isCurrent) {
15581558
setBio(result);
15591559
}
15601560
});
15611561
return () => {
1562-
ignore = true;
1562+
isCurrent = false;
15631563
}
15641564
}, [person]);
15651565

@@ -1593,16 +1593,16 @@ export async function fetchBio(person) {
15931593
15941594
</Sandpack>
15951595
1596-
Each render's Effect has its own `ignore` variable. Initially, the `ignore` variable is set to `false`. However, if an Effect gets cleaned up (such as when you select a different person), its `ignore` variable becomes `true`. So now it doesn't matter in which order the requests complete. Only the last person's Effect will have `ignore` set to `false`, so it will call `setBio(result)`. Past Effects have been cleaned up, so the `if (!ignore)` check will prevent them from calling `setBio`:
1596+
Each render's Effect has its own `isCurrent` variable. Initially, the `isCurrent` variable is set to `true`. However, if an Effect gets cleaned up (such as when you select a different person), its `isCurrent` variable becomes `false`. So now it doesn't matter in which order the requests complete. Only the last person's Effect will have `isCurrent` set to `true`, so it will call `setBio(result)`. Past Effects have been cleaned up, so the `if (isCurrent)` check will prevent them from calling `setBio`:
15971597
15981598
- Selecting `'Bob'` triggers `fetchBio('Bob')`
15991599
- Selecting `'Taylor'` triggers `fetchBio('Taylor')` **and cleans up the previous (Bob's) Effect**
16001600
- Fetching `'Taylor'` completes *before* fetching `'Bob'`
16011601
- The Effect from the `'Taylor'` render calls `setBio('This is Taylor’s bio')`
16021602
- Fetching `'Bob'` completes
1603-
- The Effect from the `'Bob'` render **does not do anything because its `ignore` flag was set to `true`**
1603+
- The Effect from the `'Bob'` render **does not do anything because its `isCurrent` flag was set to `false`**
16041604
1605-
In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problem.
1605+
In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `isCurrent` is the most reliable way to fix this type of problem.
16061606
16071607
</Solution>
16081608

0 commit comments

Comments
 (0)