use-rxjs-effect
Size: 6.25 kB with all dependencies, minified and gzipped as ESM module
When creating effects in React with useEffect
and Promises, setTimeout
or setInterval
, you can run into various problems. When effects still execute after the component unmounts, it can lead to:
- Wasted bandwidth uploading or downloading what the user may not be interested in anymore.
- Errors thrown when effects update DOM nodes that that don't exist after unmount.
And when useEffect
is triggered on a change of dependencies:
- You are limited in concurrency options (you can not easily queue or throttle effects)
- Cancelation of a previous effect only runs after a render cycle (leading to race conditions ala the Kent C Dodds setInterval problem here)
Adding AbortController
-based code to Promises is only a band-aid, and bloats your code. But Observables (from RxJS) are fundamentally cancelable, are concise to read, and have a full range of concurrency options. use-rxjs-effect
exists to bring the benefits of RxJS Observables to React's useEffect
.
API
These methods, sharing the same signature, allow you to transparently apply component-based cancelation, and RxJS concurrency modes, to effects:
-
useASAPEffect
- Runs effects in parallel as order-independent, unconstrained effects. Use Case: Like Buttons.RxJS operator: mergeMap
-
useQueuedEffect
- Runs effects serially, with a maximum concurrency of 1. Use Case: Analytics Events, ensure arrival order is consistent with sending order.RxJS operator: concatMap
-
useDebouncedEffect
- Runs an effect with a maximum concurrency of 1, canceling any previously running effect. Wrap the returned Observable withafter(N, effectObservable)
to apply a debounce delay. Use Case: A typeahead/autocomplete search box.RxJS operator: switchMap
-
useThrottledEffect
- Runs an effect with a maximum concurrency of 1.Refrainins from starting a new effect if one is already running. Wrap the returned Observable withconcat(effectObservable, after(N))
to add to the throttling delay. Use Case: Prevent double-submission of a form.RxJS operator: exhaustMap
-
useCancelableEffect
- Alias foruseASAPEffect
. Use Case: Ensure component initialization is canceled if component is unmounted.
The usage pattern for these hooks is:
const [triggerFn] = useCancelableEffect((value: string) => {
return new Observable(() => {
// do the work
return () => // cancel the work
});
});
The return tuple contains a stable function that can be used to trigger the effect in event handlers or useEffect
s, assured that cancelation and the correct concurrency will be applied.
<input onChange={(e) => triggerFn(e.target.value)}>
Note that your Observable-returning function can be imported from another library (e.g. RxJS, or a custom one), and need not have any dependency on React. You can concat
or pipe(tap)
onto the Observable to apply React-specific behaviors like calling state-setters. See the example/
folder for more.
Utilities:
-
after(msec, value | fn | Observable)
- Creates an Observable of a value, the return value of a function, or another Observable, delayed by the number of milliseconds given. Can beawait
-ed. -
concat
- A re-export of RxJS'concat
, for creating chains of Observables with composed cancelation and values.
Examples
See executable code in the example/
directory of this project.
cd example; yarn && yarn start
Timing Diagrams
The usefulness of these fundamental modes of combining async effects can be illustrated (thanks to Ember Concurrency for the originals)