Skip to content

[FEATURE] Advanced useAsyncEffect #220

@haejunejung

Description

@haejunejung

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.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions