Reputation: 264
I'm building a react app (am a noob at javascript) and struggling with react trying to update a state after the component has been unmounted.
Context for the react app: as part of a school project I have to build a movie ticket booking service for a small theatre complex, on the home page of the web app I have an image 'carousel' which is updating the image every 5 seconds.
Here is the home page in action: https://apollo-9dd16.web.app/
(the image loading time is just because I chose rather large images, will be fixed) - also the loading screen when first accessing the page is because it takes that long to load everything from firebase (everything is dynamically updated through firebase database) if there is a way to make it quicker please let me know 🙂
Also, I've gone through as many SO posts as I can find and everyone suggests adding a check if the component is mounted before performing state changes, which I think I've done?
Anyways, onto the problem, for some reason, it will come up saying I have a memory leak as I am trying to change the components state after it is unmounted (I can't reproduce it easily but it has happened a number of times)
import React, {Component} from 'react'
import { Link } from 'react-router-dom'
import MoviesContext from '../contexts/Movies'
/* Todo:
- Work on the transition between movies
- Work on sending the user to the right place on book now button press
*/
class Home extends Component {
static contextType = MoviesContext
state = {
intervalID:0,
index:1,
prevIndex:0,
nextIndex:2,
picList:[]
}
componentDidMount() {
let mounted = true
const posterURLS = [] // eslint-disable-next-line
this.context.map((movies) => {
posterURLS.push(movies.posterURL)
})
this.setState({picList: posterURLS})
if(mounted) {
const newIntervalId = setInterval(() => {
if (this.state.nextIndex + 1 === this.state.picList.length ){
this.setState({
prevIndex: this.state.index,
index: this.state.nextIndex,
nextIndex: 0
})
} else {
this.setState({
prevIndex: this.state.index,
index: this.state.nextIndex,
nextIndex: this.state.nextIndex + 1
})
}
}, 5000);
this.setState(prevState => {
return {
...prevState,
intervalId: newIntervalId,
};
});
}
return () => mounted = false
}
render() {
return (
<div className="font-sans font-light text-theme-white text-4xl z-10 flex justify-center items-center h-screen">
<div className="h-3/4">
<div className="h-full justify-center items-center">
<div className="h-full hidden md:flex">
<img src={this.state.picList[this.state.prevIndex]} alt="this is a movie" className="h-full rounded-2xl -mx-20"/>
<img src={this.state.picList[this.state.nextIndex]} alt="this is a movie" className="h-full rounded-2xl -mx-20"/>
</div>
<div className="absolute inset-10 flex justify-center items-center h-screen">
<img src={this.state.picList[this.state.index]} alt="this is a movie" className="h-3/4 rounded-2xl"/>
</div>
</div>
<div className="absolute inset-0 top-10 flex justify-center items-center">
<Link to="/book" className="bg-theme-light text-theme-black rounded-2xl py-3 px-5 hover:bg-theme-black hover:text-theme-light">Book Now</Link>
</div>
</div>
</div>
)
}
}
export default Home
Upvotes: 1
Views: 153
Reputation: 444
You're pretty close! The reason that this is happening is because of this code:
/* 1 */ if (mounted) {
/* 2 */ const newIntervalId = setInterval(() => {
/* 3 */ if (this.state.nextIndex + 1 === this.state.picList.length) {
/* 4 */ this.setState({
/* 5 */ prevIndex: this.state.index,
/* 6 */ index: this.state.nextIndex,
/* 7 */ nextIndex: 0,
/* 8 */ });
/* 9 */ } else {
/* 10 */ this.setState({
/* 11 */ prevIndex: this.state.index,
/* 12 */ index: this.state.nextIndex,
/* 13 */ nextIndex: this.state.nextIndex + 1,
/* 14 */ });
/* 15 */ }
/* 16 */ }, 5000);
/* 17 */
/* 18 */ this.setState((prevState) => {
/* 19 */ return {
/* 20 */ ...prevState,
/* 21 */ intervalId: newIntervalId,
/* 22 */ };
/* 23 */ });
/* 24 */ }
/* 25 */
The if (mounted)
condition on line 1 (in the above snippet) is up a bit too high. You should wrap the if statement around the setState
calls on lines 4 and 10 as these are both inside the setInterval
function callback.
Currently, the code is saying:
But what I believe you'd like your code to say:
if (mounted) {
const newIntervalId = setInterval(() => {
if (this.state.nextIndex + 1 === this.state.picList.length) {
if (mounted) {
this.setState({
prevIndex: this.state.index,
index: this.state.nextIndex,
nextIndex: 0,
});
}
} else {
if (mounted) {
this.setState({
prevIndex: this.state.index,
index: this.state.nextIndex,
nextIndex: this.state.nextIndex + 1,
});
}
}
}, 5000);
this.setState((prevState) => {
return {
...prevState,
intervalId: newIntervalId,
};
});
}
That should fix the component update after the component is already unmounted. You might even be able to remove the if (mounted)
check at the start of the above snippet.
The reason it's happening is that at the time the componentDidMount
function is executed the mounted
variable is true
, so it'll register the setInterval
callback and keeps executing it every 5 seconds.
While the mounted
variable may no longer be true
when the setInterval
callback is executed, there's nothing stopping it from executing.
Upvotes: 1