首页 > 解决方案 > In React, failing to stop Axios request when component unmounts

问题描述

With data fetching in React, the following is a common warning:

Warning: Can't perform a React state update 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 a useEffect cleanup function. in ParentComponent

I've read multiple posts and suggestions on how to handle this, and none are working currently.

For this, we have the function useAxiosApi which fetches data asynchronously, and ParentComponent which is the component that uses the useAxiosApi() and needs the data. ParentComponent is the component being unmounted / being referenced in the warnings.

Parent Component

import useAxiosApi...
function ParentComponent({ info }) {
    const dataConfig = { season: info.season, scope: info.scope };
    const [data, isLoading1, isError1] = useAxiosApi('this-endpoint', [], dataConfig);

    return (
        {isLoading && <p>We are loading...</p>}
        {!isLoading &&
            ... use the data to render something...
        }
    )
}

useAxiosApi

import axios from 'axios';
import { useState } from 'react';
import useDeepCompareEffect from 'use-deep-compare-effect';

const resources = {};
const useAxiosApi = (endpoint, initialValue, config) => {
    // Set Data-Fetching State
    const [data, setData] = useState(initialValue);
    const [isLoading, setIsLoading] = useState(true);
    const [isError, setIsError] = useState(false);

    // Use in lieu of useEffect
    useDeepCompareEffect(() => {
        // Token/Source should be created before "fetchData"
        let source = axios.CancelToken.source();
        let isMounted = true;

        // Create Function that makes Axios requests
        const fetchData = async () => {
            // For Live Search on keystroke, Save Fetches and Skip Fetch if Already Made
            if (endpoint === 'liveSearch' && resources[config.searchText]) {
                return [resources[config.searchText], false, false];
            }

            // Otherwise, Continue Forward
            setIsError(false);
            setIsLoading(true);
            try {
                const url = createUrl(endpoint, config);
                const result = await axios.get(url, { cancelToken: source.token });

                console.log('isMounted: ', isMounted);
                if (isMounted) {
                    setData(result.data);
                }

                // If LiveSearch, store the response to "resources"
                if (endpoint === 'liveSearch') {
                    resources[config.searchText] = result.data;
                }
            } catch (error) {
                setIsError(true);
            } finally {
                setIsLoading(false);
            }
        };

        // Call Function
        fetchData();

        // Cancel Request if needed in cleanup function
        return () => {
            console.log('Unmount or New Search? About to call source.cancel()');
            isMounted = false; // is this doing its job?
            source.cancel();
        };
    }, [endpoint, config]);

    // Return as length-3 array
    return [data, isLoading, isError];
};

export default useAxiosApi;

createUrl is simply a function that takes the endpoint and dataConfig and creates the url that axios will fetch from. Note that our cancelTokens seem to be working in conjunction with the Live search, as new searches are cancelling the old search queries, and the saving of data results into resources for the one specific endpoint liveSearch works as well.

However, our problem is that when ParentComponent is unmounted quickly, before the data fetch is complete, we still receive the Cant perform a React state update warning. I've checked the console.logs(), and console.log('isMounted: ', isMounted) is always returning true, even if we unmount the component quickly after it is mounted / before data fetching is complete.

We're at a loss on this, as using the isMounted variable is the way that I've seen this problem handled before. Perhaps there's a problem with the useDeepCompareEffect hook? Or maybe we're missing something else.

Edit: Weve also tried to create the isMounted variable from inside of ParentComponent, and pass that as a parameter into the useAxiosApi function, however this did not work for us either... In general, it would be much better if we can handle this warning via an update to our useAxiosApi function, as opposed to in the ParentComponent.

Edit2: It seems like the cancelToken only works when a duplicate API call is fired off to the same endpoint. This is good for our liveSearch, however it means that all of the other fetches are not cancelled.

标签: javascriptreactjsmemory-leaksaxios

解决方案


推荐阅读