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.
The warning "Can't perform a React state update on an unmounted component" is caused when we try to update the state of an unmounted component. A straight forward way to get rid of the warning is to keep track of whether the component is mounted using an isMounted boolean in our useEffect hook.
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.
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.
To clean up after a component unmounts, we have a simple way to perform the equivalent of the componentWillUnmount using the useEffect Hook. The only thing that we need to do is to return a function inside the callback function of the useEffect Hook like this: useEffect(() => { window.
For me, clean the state in the unmount of the component helped.
const [state, setState] = useState({}); useEffect(() => { myFunction(); return () => { setState({}); // This worked for me }; }, []); const myFunction = () => { setState({ name: 'Jhon', surname: 'Doe', }) }
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With