-
Notifications
You must be signed in to change notification settings - Fork 57
Description
Is your feature request related to a problem? Please describe.
useAsyncEffect is a hook designed to manage asynchronous side effects. However, in real-word scenarios involving asynchronous operations such as fetch, it is crucial to abort ongoing operations when a component unmounts or when dependencies change.
Currently, to handle this properly, developers must manually create an AbortController and call abort() within the useAsyncEffect like this:
useAsyncEffect(async () => {
const abortController = new AbortController();
const data = await fetchData({ signal: abortController.signal });
setData(data);
return () => {
abortController.abort(reason);
console.log('Cleanup on unmount or dependencies change');
};
}, [dependencies]);This approach introduces several problems
- Repeatedly write boilerplate code to manage the
AbortController - A potential risk of missing abort handling
Describe the solution you'd like
I propose enhancing useAsyncEffect so that it automatically creates and manages an AbortController internally. This way, the developer simply receives the AbortSignal in the effect callback and passes it into any asynchronous operations that support aborting.
For example, using useAsyncEffect after this improvement would look like:
useAsyncEffect(async (signal) => {
const response = await fetch("/api/data", { signal });
const data = await response.json();
setData(data);
return () => {
console.log('Cleanup on unmount or dependencies change');
};
}, [dependencies], reason);Benefits of this approach
- No need to manually create or manage AbortControllers
- Developers can write more declarative and cleaner asynchronous effects
- Prevents memory leaks and invalid state updates safely and consistently.
Describe alternatives you've considered
Here's the improved version:
export function useAsyncEffect(
effect: (signal: AbortSignal) => Promise<void | (() => void)>,
deps?: DependencyList,
reason: any,
) {
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
let cleanup: (() => void) | void;
effect(signal).then((result) => {
if (!signal.aborted) {
cleanup = result;
}
});
return () => {
abortController.abort(reason);
cleanup?.();
};
}, deps);
}Additional context
AbortController and AbortSignal are widely supported in all modern browser. Reference, https://developer.mozilla.org/en-US/docs/Web/API/AbortController.