最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Correct way to cancel async axios request in a React functional component - Stack Overflow

programmeradmin8浏览0评论

What is the correct approach to cancel async requests within a React functional ponent?

I have a script that requests data from an API on load (or under certain user actions), but if this is in the process of being executed & the user navigates away, it results in the following warning:

Warning: Can't perform a React state update on an unmounted ponent. 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.

Most of what I have read solves this with the AbortController within the ponentDidUnmount method of a class-based ponent. Whereas, I have a functional ponent in my React app which uses Axois to make an asynchronous request to an API for data.

The function resides within a useEffect hook in the functional ponent to ensure that the function is run when the ponent renders:

  useEffect(() => {
    loadFields();
  }, [loadFields]);

This is the function it calls:

  const loadFields = useCallback(async () => {
    setIsLoading(true);
    try {
      await fetchFields(
        fieldsDispatch,
        user.client.id,
        user.token,
        user.client.directory
      );
      setVisibility(settingsDispatch, user.client.id, user.settings);
      setIsLoading(false);
    } catch (error) {
      setIsLoading(false);
    }
  }, [
    fieldsDispatch,
    user.client.id,
    user.token,
    user.client.directory,
    settingsDispatch,
    user.settings,
  ]);

And this is the axios request that is triggered:


async function fetchFields(dispatch, clientId, token, folder) {
  try {
    const response = await api.get(clientId + "/fields", {
      headers: { Authorization: "Bearer " + token },
    });

    // do something with the response

  } catch (e) {
    handleRequestError(e, "Failed fetching fields: ");
  }
}

Note: the api variable is a reference to an axios.create object.

What is the correct approach to cancel async requests within a React functional ponent?

I have a script that requests data from an API on load (or under certain user actions), but if this is in the process of being executed & the user navigates away, it results in the following warning:

Warning: Can't perform a React state update on an unmounted ponent. 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.

Most of what I have read solves this with the AbortController within the ponentDidUnmount method of a class-based ponent. Whereas, I have a functional ponent in my React app which uses Axois to make an asynchronous request to an API for data.

The function resides within a useEffect hook in the functional ponent to ensure that the function is run when the ponent renders:

  useEffect(() => {
    loadFields();
  }, [loadFields]);

This is the function it calls:

  const loadFields = useCallback(async () => {
    setIsLoading(true);
    try {
      await fetchFields(
        fieldsDispatch,
        user.client.id,
        user.token,
        user.client.directory
      );
      setVisibility(settingsDispatch, user.client.id, user.settings);
      setIsLoading(false);
    } catch (error) {
      setIsLoading(false);
    }
  }, [
    fieldsDispatch,
    user.client.id,
    user.token,
    user.client.directory,
    settingsDispatch,
    user.settings,
  ]);

And this is the axios request that is triggered:


async function fetchFields(dispatch, clientId, token, folder) {
  try {
    const response = await api.get(clientId + "/fields", {
      headers: { Authorization: "Bearer " + token },
    });

    // do something with the response

  } catch (e) {
    handleRequestError(e, "Failed fetching fields: ");
  }
}

Note: the api variable is a reference to an axios.create object.

Share Improve this question edited Jun 23, 2020 at 16:11 ford04 74.6k25 gold badges216 silver badges176 bronze badges asked Jun 23, 2020 at 11:12 SheixtSheixt 2,62613 gold badges38 silver badges66 bronze badges 0
Add a ment  | 

3 Answers 3

Reset to default 12

To Cancel a fetch operation with axios:

  1. Cancel the request with the given source token
  2. Ensure, you don't change ponent state, after it has been unmounted

Ad 1.)

axios brings its own cancel API:

const source = axios.CancelToken.source();
axios.get('/user/12345', { cancelToken: source.token })
source.cancel(); // invoke to cancel request

You can use it to optimize performance by stopping an async request, that is not needed anymore. With native browser fetch API, AbortController would be used instead.

Ad 2.)

This will stop the warning "Warning: Can't perform a React state update on an unmounted ponent.". E.g. you cannot call setState on an already unmounted ponent. Here is an example Hook enforcing and encapsulating mentioned constraint.


Example: useAxiosFetch

We can incorporate both steps in a custom Hook:

function useAxiosFetch(url, { onFetched, onError, onCanceled }) {
  React.useEffect(() => {
    const source = axios.CancelToken.source();
    let isMounted = true;
    axios
      .get(url, { cancelToken: source.token })
      .then(res => { if (isMounted) onFetched(res); })
      .catch(err => {
        if (!isMounted) return; // p already unmounted, nothing to do
        if (axios.isCancel(err)) onCanceled(err);
        else onError(err);
      });

    return () => {
      isMounted = false;
      source.cancel();
    };
  }, [url, onFetched, onError, onCanceled]);
}

import React from "react";

import axios from "axios";

export default function App() {
  const [mounted, setMounted] = React.useState(true);
  return (
    <div>
      {mounted && <Comp />}
      <button onClick={() => setMounted(p => !p)}>
        {mounted ? "Unmount" : "Mount"}
      </button>
    </div>
  );
}

const Comp = () => {
  const [state, setState] = React.useState("Loading...");
  const url = `https://jsonplaceholder.typicode./users/1?_delay=3000&timestamp=${new Date().getTime()}`;
  const handlers = React.useMemo(
    () => ({
      onFetched: res => setState(`Fetched user: ${res.data.name}`),
      onCanceled: err => setState("Request canceled"),
      onError: err => setState("Other error:", err.message)
    }),
    []
  );
  const cancel = useAxiosFetch(url, handlers);

  return (
    <div>
      <p>{state}</p>
      {state === "Loading..." && (
        <button onClick={cancel}>Cancel request</button>
      )}
    </div>
  );
};

// you can extend this hook with custom config arg for futher axios options
function useAxiosFetch(url, { onFetched, onError, onCanceled }) {
  const cancelRef = React.useRef();
  const cancel = () => cancelRef.current && cancelRef.current.cancel();

  React.useEffect(() => {
    cancelRef.current = axios.CancelToken.source();
    let isMounted = true;
    axios
      .get(url, { cancelToken: cancelRef.current.token })
      .then(res => {
        if (isMounted) onFetched(res);
      })
      .catch(err => {
        if (!isMounted) return; // p already unmounted, nothing to do
        if (axios.isCancel(err)) onCanceled(err);
        else onError(err);
      });

    return () => {
      isMounted = false;
      cancel();
    };
  }, [url, onFetched, onError, onCanceled]);
  return cancel;
}

useEffect has a return option which you can use. It behaves (almost) the same as the ponentDidUnmount.

useEffect(() => {
  // Your axios call

  return () => {
    // Your abortController
  }
}, []);

You can use lodash.debounce and try steps below

Stap 1:

inside constructor:
this.state{
 cancelToken: axios.CancelToken,
 cancel: undefined,
}
this.doDebouncedTableScroll = debounce(this.onScroll, 100);

Step 2: inside function that use axios add:

if (this.state.cancel !== undefined) {
                cancel();
            }

Step 3:

 onScroll = ()=>{
    axiosInstance()
         .post(`xxxxxxx`)
              , {data}, {
                  cancelToken: new cancelToken(function executor(c) {
                         this.setState({ cancel: c });
                        })
                   })
                    .then((response) => {
    
                         }
发布评论

评论列表(0)

  1. 暂无评论