0

I have a React component using hooks like this:

const myComponent = (props) => {
  useEffect(() => {
     FetchData()
     .then(data => {
       setState({data: data});
     }
    // some other code
  }, []);

//some other code and render method...
}

fetchData is in charge to use axios and get the data from an API:

const FetchData = async () => {
   try {
    res = await myApiClient.get('/myEndpoint);
   } catch (err) {
    console.log('error in FetchData');
    res = err.response
}
}

and finally myApiClient is defined externally. I had to use this setup in order to be able to use different APIs...

import axios from "axios";
axios.defaults.headers.post["Content-Type"] = "application/json";

const myApiClient = axios.create({
  baseURL: process.env.REACT_APP_API1_BASEURL
});
const anotherApiClient = axios.create({
  baseURL: process.env.REACT_APP_API2_BASEURL
});

export {
  myApiClient,
  anotherApiClient
};

with this setup I am getting the 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.

I googled a bit and I saw some suggestions on how to clean up requests from useEffect, like this, but my axios is defined externally. So how can I send the cancellation using this setup?

Also, the application is using redux, not sure if it is in some way involved.

Any other suggestion to avoid the error is welcome.

user3174311
  • 1,714
  • 5
  • 28
  • 66
  • 1
    https://github.com/axios/axios#cancellation – Dominic Nov 25 '19 at 10:38
  • what do you mean, "externally"? You can pass a https://www.npmjs.com/package/cancellationtoken to axios request and cancel it when necessary. – blits Nov 25 '19 at 10:39
  • I mean I dn't know how to pass the cancellationToken defined in myApiClient all the way up to the useEffect in the component. Can I just export it and then do an import? – user3174311 Nov 25 '19 at 10:44
  • the problem was caused by Redux Store. solution here: https://stackoverflow.com/questions/59034923/how-to-execute-store-unsubscribe-from-useeffect-using-react-hooks-and-redux – user3174311 Nov 25 '19 at 15:43

2 Answers2

1

You can use defer from rxjs for this:

const FetchData = () => {
  try {
    return myApiClient.get("/myEndpoint");
  } catch (err) {
    console.log("error in FetchData");
    return err.response;
  }
};

const myComponent = (props) => {
  useEffect(() => {
    const subscription = defer(FetchData()).subscribe({
      next: ({
        data
      }) => {
        setState({
          data: data
        });
      },
      error: () => {
        // error handling
      },
      complete: () => {
        // cancel loading state etc
      }
    });

    return () => subscription.unsubscribe();
  }, []);
}
Clarity
  • 10,730
  • 2
  • 25
  • 35
  • not a good idea to use `rxjs` in a project if it's not used anywhere else because of it's size – blits Nov 25 '19 at 10:40
  • 1
    I'd imagine you could import `defer` separately and utilize tree shaking to not include the whole lib. – Clarity Nov 25 '19 at 10:41
0

Alway check if you are dealing with fetch or any long operations.

let _isMounted = false;

const HooksFunction = props => {


  const [data, setData] = useState({}); // data supposed to be object

  const fetchData = async ()=> {
    const res = await myApiClient.get('/myEndpoint');
    if(_isMounted) setData(res.data); // res.data supposed to return an object
  }

  useEffect(()=> {

    _isMounted = true;

    return ()=> {
        _isMounted = false;
    }

  },[]);

  return (
    <div>
      {/*....*/}
    <div/>
  );

}
Muhammad
  • 2,572
  • 4
  • 24
  • 46
  • Sorry if I was not clear enough. FetchData is in another file and exported to be used from useEffect that is in a component. thanks – user3174311 Nov 25 '19 at 12:06
  • No matter it is exported or not, you just have to check `if(_isMounted)` then update the state. – Muhammad Nov 25 '19 at 12:14