Cleanup asynchronous tasks in React

By | September 26, 2020

If you are a React developer, you might see this error:
Warning: Can’t call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

In React, components maintain their own state during its lifecycle. Like props, each state change will trigger an event to inform component to re-render with new data. That leads to a problem: if the event occurs after component unmounted, it causes a memory leak in our application. Therefore, cancelling all asynchronous tasks and subscriptions are very important to ensure our application to work properly.
In this article, we will not go deep in what an asynchronous tasks are, just focus on how to cancel them carefully in our components.

Identify asynchronous tasks that need to be cancelled

Every action that not return a result immediately is an asynchronous task. They are:

  • Network calls
  • Geolocation subscription
  • Message queue subscription
  • An interval loop

  • However, you do not need to cancel everything, just need to care about tasks which will trigger state changes when finishing. See this example:

    React.useEffect(
        () => {
            repository.get('products')
                .then((products) => {
                    setProducts(products);
                });
        },
        [],
    )

    Cancel a promise-like action

    Promise has become a basic concept of Javascript since ES5. It does not provide a cancelling mechanism itself. To cancel a promise that trigger a state change on component unmount, we need to detect when component unmount manually to decide executing a state change or not:

    React.useEffect(
        () => {
            let cancelled: boolean = false;
            repository.get('products')
                .then((products) => {
                    if (!cancelled) {
                        setProducts(products);
                    }
                });
    
            return function cleanup() {
                cancelled = true;
            }
        },
        [],
    )

    In this example, cancelled is a boolean value that will change on component unmount. Before triggering any state change, we have to check if cancelled is true or not. If true, component are unmounted, we will not trigger state changes. Otherwise, component still remains, we can trigger state changes safely.

    Cancel an observable

    In React3L, we recommend to use observable (from RxJS) whenever possible. Because we choose using axios as our XHR library, we use a wrapper of axios: axios-observable to get benefits from RxJS Observable without re-implementation.
    To cancel an observable, we do it as below:

    In an effect hook:

        React.useEffect(
            () => {
                const subscription: Subscription = new Subscription();
    
                subscription.add(
                    repository.get('data')
                        .subscribe(
                            (data) => {
                                setData(data);
                            },
                        )
                );
    
                return function cleanup() {
                    subscription.unsubscribe();
                }
            },
            [],
        )

    In a callback hook:

    const subscription: Subscription = React.useRef(new Subscription()).current;
    const handleState = React.useCallback(
        () => {
            subscription.add(
                repository.get('data')
                        .subscribe(
                            (data) => {
                                setData(data);
                            },
                        ),
            );
        },
        [],
    )
    
    React.useEffect(
        () => {
            return function cleanup() {
                subscription.unsubscribe();
            }
        },
        []
    )

    Though adding code to cancel asynchronous tasks may make our code seems to be more complex, it is neccessary to do it carefully to make sure our applications work well.
    Happy coding!.

Leave a Reply