skip to Main Content

I have a component that has 3 charts(graphs), using React.js as a framework, Highcharts and HighchartsReact to create the charts.
To populate data to these charts, I make 3 requests to the server, to provide data for each chart.
When the component mounts, it makes the requests(using Axios), awaits for the response, passes the data to the chart, and then displays/draws in the DOM.

The way the app is structured:
Axios files:

  • utils(get, post, put calls)
  • interceptors

Components:

  • App.jsx
    • Root.jsx
      • Charts.jsx
      • Other pages.jsx

Root.jsx:
its in this component that the chart component and other pages will display).
It has a navbar that, for example, when you click in the charts icon in the navbar, it will redirect to the chart page.
The navbar is always there.
Using react-router-dom to deal with the re-directions.

The problem:
While the requests are being made, if I access another page(same app), and the component unmounts, I get the error:
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.

How do I deal with this?
Should I:

  • Stop the requests on url change?
  • Stop requests when the component unmounts?
  • Other solutions??

I tried abortController but couldn’t make it work.
Honestly, I don’t even know if it makes sense to use it in my case.

// axios utils
export const apiGetCall = (url) => axiosInstance.get(url);

// One of the function to get data from server and pass to chart

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await apiGetCall("/departments");
      const departmentsData = response.data.departments;

      // Update the state with the fetched department data
      setDepartmentData(departmentsData);

      // Move the contents of pieChartOptions to the useState hook
      setPieChartOptions({
        // Department data
      });
    } catch (err) {
        console.log(err);
    }
  };

  fetchDepartmentData();
}, []);

I just inserted the relevant code. The chart display etc. is working fine.
I just need help dealing with the error.

2

Answers


  1. How do I deal with this? Should I:

    • Stop the requests on url change?
    • Stop requests when component unmounts?
    • Other solutions??

    Another option is to upgrade to version 18 of react, which removed this warning. It was removed, in part, because it can be misleading: your code doesn’t actually mave a memory leak. You fetch data once, set state, then return. At that point, javascript can clean up closure variables. A persistent subscription could result in a memory leak (eg, a websocket that you forget to teardown), but you aren’t doing that.

    If you’d like to get rid of the warning without changing react versions, then you should add a cleanup function to your effect. You’ll save a variable indicating it has unmounted, and then skip setting state if that variable has been set.

    useEffect(() => {
      let cancelled = false;
      const fetchData = async () => {
        try {
          const response = await apiGetCall("/departments");
          if (cancelled) {
            return;
          }
          const departmentsData = response.data.departments;
    
          // Update the state with the fetched departmentData
          setDepartmentData(departmentsData);
    
          // Move the contents of pieChartOptions to the useState hook
          setPieChartOptions({
            // Department data
          });
        } catch (err) {
          console.log(err);
        }
      };
    
      fetchData();
    
      return () => {
        cancelled = true;
      };
    }, []);
    
    Login or Signup to reply.
  2. This warning is indicating that some asynchronous code/callback is still running even after the component that initiated the call has since unmounted. When a component unmounts you should cleanup these "connections" so there are no resource leaks.

    In the past an "isMounted" check was established as a way to check if the component was still mounted and conditionally not attempt state updates, but this has since fallen out of recommendation and is considered a React anti-pattern.

    Here you should use an abort token to cancel any inflight network requests when the component unmounts. (Pattern is very similar to the older "isMounted" logic).

    See the AbortController and Axios Cancellation docs for details.

    Example implementation:

    // axios utils
    // (0) Update apiGetCall function to consume an options object arg
    export const apiGetCall = (url, options) => axiosInstance.get(url, options);
    
    // One of the function to get data from server and pass to chart
    useEffect(() => {
      // (1) Create an abort controller
      const controller = new AbortController();
    
      const fetchData = async () => {
        try {
          const { data } = await apiGetCall(
            "/departments",
            {
              // (2) Pass controller signal in options to GET call
              signal: controller.signal
            }
          );
    
          const departmentsData = data.departments;
    
          // Update the state with the fetched department data
          setDepartmentData(departmentsData);
    
          // Move the contents of pieChartOptions to the useState hook
          setPieChartOptions({
            // Department data
          });
        } catch (err) {
          // (4) Aborted request Promise rejection, cancel error
          console.log(err);
        }
      };
    
      fetchData();
    
      return () => {
        // (3) Abort in-flight request on component unmount
        controller.abort();
      };
    }, []);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search