When fetching data I’m getting: Can’t perform a React state update on an unmounted component. The app still works, but react is suggesting I might be causing a memory leak.
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.”
Why do I keep getting this warning?
I tried researching these solutions:
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
but this still was giving me the warning.
const ArtistProfile = props => { const [artistData, setArtistData] = useState(null) const token = props.spotifyAPI.user_token const fetchData = () => { const id = window.location.pathname.split("/").pop() console.log(id) props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10) .then(data => {setArtistData(data)}) } useEffect(() => { fetchData() return () => { props.spotifyAPI.cancelRequest() } }, []) return ( <ArtistProfileContainer> <AlbumContainer> {artistData ? artistData.artistAlbums.items.map(album => { return ( <AlbumTag image={album.images[0].url} name={album.name} artists={album.artists} key={album.id} /> ) }) : null} </AlbumContainer> </ArtistProfileContainer> ) }
Edit:
In my api file I added an AbortController()
and used a signal
so I can cancel a request.
export function spotifyAPI() { const controller = new AbortController() const signal = controller.signal // code ... this.getArtist = (id) => { return ( fetch( `https://api.spotify.com/v1/artists/${id}`, { headers: {"Authorization": "Bearer " + this.user_token} }, {signal}) .then(response => { return checkServerStat(response.status, response.json()) }) ) } // code ... // this is my cancel method this.cancelRequest = () => controller.abort() }
My spotify.getArtistProfile()
looks like this
this.getArtistProfile = (id,includeGroups,market,limit,offset) => { return Promise.all([ this.getArtist(id), this.getArtistAlbums(id,includeGroups,market,limit,offset), this.getArtistTopTracks(id,market) ]) .then(response => { return ({ artist: response[0], artistAlbums: response[1], artistTopTracks: response[2] }) }) }
but because my signal is used for individual api calls that are resolved in a Promise.all
I can’t abort()
that promise so I will always be setting the state.
Advertisement
Answer
Sharing the AbortController
between the fetch()
requests is the right approach.
When any of the Promise
s are aborted, Promise.all()
will reject with AbortError
:
function Component(props) { const [fetched, setFetched] = React.useState(false); React.useEffect(() => { const ac = new AbortController(); Promise.all([ fetch('http://placekitten.com/1000/1000', {signal: ac.signal}), fetch('http://placekitten.com/2000/2000', {signal: ac.signal}) ]).then(() => setFetched(true)) .catch(ex => console.error(ex)); return () => ac.abort(); // Abort both fetches on unmount }, []); return fetched; } const main = document.querySelector('main'); ReactDOM.render(React.createElement(Component), main); setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script> <main></main>