Gerald Ylli
Gerald Ylli

Reputation: 116

React - To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function

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.

Upvotes: 1

Views: 1726

Answers (3)

Dmitriy Mozgovoy
Dmitriy Mozgovoy

Reputation: 1597

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>;
}

Upvotes: 1

Maielo
Maielo

Reputation: 732

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

Upvotes: 1

AlexZvl
AlexZvl

Reputation: 2302

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

Upvotes: 1

Related Questions