react: useEffect memory leak when setting state in fetch promise
Do you want to request a feature or report a bug? Reporting a possible bug
What is the current behavior? My app renders fine with no errors but I can’t seem to figure out why I keep getting this warning:
index.js:1446 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. in ArtistProfile (at App.js:51) in component (created by Route)
api-calls.js (Here’s a link): https://github.com/ryansaam/litphum/blob/master/src/api-calls.js
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
user: {},
spotifyAPI: {}
}
}
componentDidMount() {
if (user_token) {
sessionStorage.setItem('access_token', user_token)
this.setState({
spotifyAPI: new spotifyAPI( user_token )
})
} else if (sessionStorage.getItem('access_token')) {
this.setState({
spotifyAPI: new spotifyAPI( sessionStorage.getItem('access_token') )
})
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.spotifyAPI !== prevState.spotifyAPI)
this.state.spotifyAPI.getUserProfile()
.then(data => this.setState({user: data}))
}
render() {
const { user, spotifyAPI } = this.state
const token = sessionStorage.getItem('access_token')
return (
<Router>
<div className="App">
{ (spotifyAPI.user_token && user)
? (<div className="logged-in">
<div style={{width: "250px", height: "100%", position: "relative", float: "left"}} >
<Nav image={user.images ? user.images[0].url : null} user={user} />
</div>
<main id="main">
<Route path={`/${user.type}/${user.id}`} exact component={() => <Home spotifyAPI={spotifyAPI} />} />
<Route path="/artist/" component={() => <ArtistProfile spotifyAPI={spotifyAPI} />} />
</main>
</div>)
: <div onClick={() => window.location = "http://localhost:8888/login"} >log in</div>
}
</div>
</Router>
);
}
}
ArtistProfile.js
const ArtistProfile = props => {
const [artistData, setArtistData] = useState(null)
const { getArtist, getArtistAlbums, getArtistTopTracks } = props.spotifyAPI
useEffect(() => {
const id = window.location.pathname.split("/").pop()
const ac = new AbortController()
console.log(id)
Promise.all([
getArtist(id, ac),
getArtistAlbums(id, ["album"],"US", 10, 0, ac),
getArtistTopTracks(id, "US", ac)
])
.then(response => {
setArtistData({
artist: response[0],
artistAlbums: response[1],
artistTopTracks: response[2]
})
})
.catch(ex => console.error(ex))
return () => ac.abort()
}, [])
console.log(artistData)
return (
<div>
<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>
</div>
)
}
What is the expected behavior? If you can see in ArtistProfile.js I am using a clean up function that aborts when the component does unmount. The fetch would be aborted and state shouldn’t update but for some reason I am still getting this memory leak warning.
What I am expecting is for the warning to no longer throw because am using a clean up function that aborts the fetch.
Link to repo: https://github.com/ryansaam/litphum
- Files mentioned App.js: https://github.com/ryansaam/litphum/blob/master/src/App.js ArtistProfile.js: https://github.com/ryansaam/litphum/blob/master/src/components/ArtistProfile.js api-calls.js: https://github.com/ryansaam/litphum/blob/master/src/api-calls.js
My stackoverflow question: https://stackoverflow.com/questions/54954385/react-useeffect-causing-cant-perform-a-react-state-update-on-an-unmounted-comp/54964237#54964237
Which versions of React, and which browser React 16.8.2 Latest version of Chrome
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 31 (3 by maintainers)
same question
cancel promise solution
Let’s keep it open though. There might be a false positive warning in some cases.
It would really help if you turned this into a CodeSandbox…
To avoid calling setState after unmount from a hook, you can use the useEffect callback to set a ref value on unmount, and use that as a guard in your promise callbacks to check whether the component is still mounted before updating state. Here’s an example: https://github.com/ghengeveld/react-async/blob/master/src/useAsync.js
But to be honest you’re probably better off not reinventing this wheel and use useAsync directly, because it’s fully tested and covers other edge cases as well.
It is obvious that if this simple trick makes the difference it’s because React is bad designed. They cared so much about the management of the state that the state ended up managing them.
Yes but what if you’re dispatching actions to a context and want to dispatch even if the component unmounts ? For exemple you have a popin with a form that change the color of your website. You would still want to update the website color even if the user has closed the popin while the API call was still being processed
Well yeah this would work but that’s not the point, you could have a context that is not always mounted. The question being “What happens when react prints this warning”, is it harmful in some way ? If so how should we handle a
isMounted
boolean in the contexts ?React 18 does not have this warning because it’s often pointless. (Such as in this case.)
I have the similar issue, I have canceled the axios request, but I get this Warning only on the first unmount of a component. This warning does not happen on the next umount of a component.
See below codes:
import React, { useEffect } from “react”; import CurrentOperatingStatus from “./current-operating-status” import { useDataApi } from ‘./lib/data-api’ import Controllers from ‘./cos-table’
export default function ContentOverall() { const [{ data, isLoading, isError, }, doFetch] = useDataApi( ‘/rest/machine-list’);
} ----data-api.js---- import { useState, useEffect } from ‘react’; import axios from “./axios”;
const useDataApi = (initialUrl) => { const [data, setData] = useState(null); const [url, setUrl] = useState(initialUrl); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const CancelToken = axios.CancelToken; const source = CancelToken.source();
};
export { useDataApi };
How to clean up subscriptions in react components using AbortController https://medium.com/@selvaganesh93/how-to-clean-up-subscriptions-in-react-components-using-abortcontroller-72335f19b6f7
Alright I will try my best to recreate it maybe using another API like a weather API. The only reason I couldn’t here was because I was using Spotify and you need to have an account to get an access token
Given that the latest release was 17.0.2 (March 22, 2021) and this warning was removed on Aug 18 https://github.com/facebook/react/pull/22114 – this issue is not going to be an issue someday based on that eh
I dont understand the solutions given here. Am also facing same issue
@TomPradat What’s harmful is the lack of one’s thought alignment with
useEffect
. The docs haven’t been updated since adaptation phase, so they still say it’s a cool replacement of lifecycle methods. It’s tricky understanding what’s under the hood just like flux was when it first came out, but you should get right to it instead of circling around the warning.The solution to this problems it to use a flag to check if a component is unmounted, avoid setting state update, as the following in a useEffect hook.
I tried to recreate the warning but I’m not getting it to show in this demo https://codesandbox.io/s/r5l297q3oq?fontsize=14