Now that React Hooks have been officially released, even more patterns are emerging across the Internet.
useEffect
The useEffect
hook’s among the most popular, as it can replace componentDidMount
, componentDidUpdate
, and componentWillUnmount
.
Most of the initialization, updates, and cleanup logic a component may need can be put inside of useEffect
.
An Ugly User Experience
On a recent project, I encountered a scenario where useEffect
acted on HTTP requests I was no longer interested in.
Conceptually, the UI was like this:
-
- On first load, fetch the list of fruits and render a
<button>
for each one.
- On first load, fetch the list of fruits and render a
- Click a
<button>
to fetch that fruit’s details.
But watch what happens when I click multiple fruits in a row
Way after I stopped clicking, the fruit detail section kept changing!
The Code
Let’s see my custom hook that leverages useEffect
.
Here’s the Codesandbox and GitHub links if you wish to follow along. The file is useFruitDetail.js
.
import { useEffect, useState } from 'react'; import { getFruit } from './api';
export const useFruitDetail = fruitName => { const [fruitDetail, setFruitDetail] = useState(null);
useEffect( () => { if (!fruitName) { return; }
getFruit(fruitName).then(setFruitDetail); }, [fruitName] );
return fruitDetail; };
Whenever fruitName
changes, we’ll request its details. And we have no way of cancelling a request! So quickly re-running this results in many state changes that we’re no longer interested in.
If you render this to the UI, you get a messy user experience where the detail section keeps flickering until the final request is resolved.
Enter RxJS
Ignoring old requests is trivial with RxJS.
It can do so much more than what I’ll demo here, so I highly recommend you dive into it!
This portion of our code, the effect code, needs to change.
() => { if (!fruitName) { return; }
getFruit(fruitName).then(setFruitDetail); }
Instead of a Promise, let’s convert getFruit
into an Observable using the RxJS defer
function. And instead of .then
, we’ll call .subscribe
.
import { defer } from 'rxjs';
...
() => { if (!fruitName) { return; }
defer(() => getFruit(fruitName)) .subscribe(setFruitDetail); }
This doesn’t fix the issue yet. We still need to unsubscribe if fruitName
changes.
According to React’s docs, we can return a function that’ll be executed at the end of our effect. This acts as the cleanup logic.
So something like this:
() => { if (!fruitName) { return; }
const subscription = defer(() => getFruit(fruitName)) .subscribe(setFruitDetail);
return () => { subscription.unsubscribe(); }; }
It Works!
This experience is much cleaner!
By clicking another fruit, useEffect
sees fruitName
change and runs the previous effect’s cleanup logic. As a result, we unsubscribe from the previous fetch call and focus on the current one.
Now our UI patiently waits until the user’s done clicking and the latest fruit’s details return.
Thanks for following this tutorial to the end! I’m on Twitter if you’d like to talk!
Take care,
Yazeed Bzadough
http://yazeedb.com/
Source: https://medium.freecodecamp.org/how-to-easily-cancel-useeffect-http-calls-with-rxjs-d1be418014e8
Written by
Yazeed Bzadough
freeCodeCamp.org
Stories worth reading about programming and technology from our open source community.