1
votes

I have the following code

const Companies = () => {
  const [company, setCompany] = useState(plainCompanyObj);
  const [companiesData, setCompaniesData] = useState([]);
  
  useEffect(() => {
    Call<any, any>({
      url:
        _baseApiUrl +
        "-------api goes here --------",
      method: "GET",
      data: null,
      success: (companies) => {
        setCompaniesData(companies.companies);
      },
      authorization: sessionStorage.getItem("accessToken"),
    });
  }, []);

  return (
    //return JSX
  );
};

I get the following 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.

Also just to clarify the Call tag is a method imported by axios that I use for URL error handler etc. The problem as the error suggests is in the useEffect section.

3
When do you exactly get this error message?Nemanja Lazarevic
@NemanjaLazarevic I only have one useEffect in my project so this should be itGerald Ylli
You could post the code where you render the <Companies /> component. Maybe in there, you have some logic that unmounts your <Companies /> component prematurely.Nemanja Lazarevic
@NemanjaLazarevic There is another component called LayoutCompanies which returns a Box from MatUI. <Box> <Companies /> </Box>Gerald Ylli
Obviously, you do not expect this component to unmount (which the error suggests). To confirm that this is the case you can add a return () => {console.log("Component Unmounded")} at the bottom of your useEffect. If after refresh you see this log, means you have to search for the culprit somewhere up in the component's tree.Nemanja Lazarevic

3 Answers

1
votes

Your problem is that you are calling setCompaniesData when component was already unmounted. So you can try using cancel token, to cancel axios request. https://github.com/axios/axios#cancellation

const Companies = () => {
  const [company, setCompany] = useState(plainCompanyObj);
  const [companiesData, setCompaniesData] = useState([]);
  
  useEffect(() => {
    const cancelTokenSource = axios.CancelToken.source();

    Call<any, any>({
      url:
        _baseApiUrl +
        "-------api goes here --------",
      method: "GET",
      data: null,
      cancelToken: cancelTokenSource.token
      success: (companies) => {
        setCompaniesData(companies.companies);
      },
      authorization: sessionStorage.getItem("accessToken"),
    });
  }, []);

  return () => {
       cancelTokenSource.cancel();
  }
};

Second option is to keep track of mounted state of the component, and only call setState if the component is still mounted. Check this video here: https://www.youtube.com/watch?v=_TleXX0mxaY&ab_channel=LeighHalliday

1
votes

The problem you are having is that, you create request to server meanwhile user/you unmount (change view/rerender) the component which is waiting for data from server.

Canceling ajax requests is good practice, its not just react thing.

Axios uses cancel tokens for canceling - canceling

From my experience it is better to wrap whole axios into your own code. And handle cancelation there. (something like api.get(/route); api.cancel(/route))

Hope it helps

1
votes

Here is a generic json axios fetch example with async task cancellation (Live demo):

import React, { useEffect, useState } from "react";
import { CPromise, CanceledError } from "c-promise2";
import cpAxios from "cp-axios";

function MyComponent(props) {
  const [text, setText] = useState("fetching...");

  useEffect(() => {
    console.log("mount");
    const promise = CPromise.from(function* () {
      try {
        const response = yield cpAxios(props.url);
        setText(`Success: ${JSON.stringify(response.data)}`);
      } catch (err) {
        console.warn(err);
        CanceledError.rethrow(err); //passthrough
        // handle other errors than CanceledError
        setText(`Failed: ${err}`);
      }
    });

    return () => {
      console.log("unmount");
      promise.cancel(); // cancel async task
    };
  }, [props.url]);

  return <p>{text}</p>;
}