How can we setState in componentDidMount
hook within an argument?
My problem is the warning of React notify that:
Warning: Can't call setState (or forceUpdate) 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 the
componentWillUnmount method.
in DiscoverPage (created by Route)
In my DiscoverPage
component:
import React, { Component } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import TvShows from './TvShows';
import Movies from './Movies';
import MovieDetail from "../MoviePage/MovieDetail";
import TvDetail from "../TvShowPage/TvDetail";
class DiscoverPage extends Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
}
getDataItem = (data) => {
this.setState({ data: data });
};
render() {
return (
<Switch>
<Route exact path="/discover/movie" render={props => <Movies data={this.getDataItem} {...props} />} />
<Route path="/discover/tv" render={props => <TvShows data={this.getDataItem} {...props} />} />
<Route path="/movie/:movie" render={props => <MovieDetail data={this.state.data} {...props} />} />
<Route path="/tv/:tv" render={props => <TvDetail data={this.state.data} {...props} />} />
<Redirect to="/discover/movie" />
</Switch>
);
}
}
export default DiscoverPage;
In Child component (this case is Movies
component):
import React, { Component } from 'react';
import requestApi from '../api';
import MovieList from "../MoviePage/MovieList";
class Movies extends Component {
constructor(props) {
super(props);
this.state = {
data: {
page: 1,
results: []
}
}
}
componentDidMount() {
requestApi.fetchData('discover', 'movie').then(response => {
this.setState({ data: response.data, isLoading: false });
});
}
getMoviebyId = (id) => {
requestApi.fetchDataById('movie', id).then(response => {
this.props.data(response.data);
});
};
nextPage = (e) => {
const page = this.state.data.page + 1;
requestApi.fetchDataPaginate('discover', 'movie', page).then(response => {
this.setState({ data: response.data });
});
};
prevPaginate = (e) => {
const page = this.state.data.page - 1;
requestApi.fetchDataPaginate('discover', 'movie', page).then(response => {
this.setState({ data: response.data });
});
};
render() {
return (
<div className="container">
<div className="ss_media">
<h2 className="title">Discover New Movies & TV Shows</h2>
<MovieList
movie={this.getMoviebyId}
routeProps={this.props}
prevPaginate={this.prevPaginate}
nextPaginate={this.nextPage}
moviesList={this.state.data} />
</div>
</div>
);
}
}
export default Movies;
The warning you're getting is telling you that you can't call setState
on an unmounted component, i.e. a component that's not currently rendered. Most likely, one of your other components (Movies
, TvShows
, etc.) is calling the method you pass as the data
prop. When one of these components is rendered, the DiscoveryPage
component is not, so calling the getDataItem
through the data
prop will result in DiscoveryPage
's setState
method to be called, when it's not rendered. To fix this (assuming that what you want to do is pass data from component to component) I'd suggest maybe using localStorage
to store a shared variable (or, as @Anu suggested, some other kind of store).
I don't understand what you mean about "in arguments", but, answering your question, you can call setState
in any component...
method (even though calling it in componentWillUnmount
makes little to no sense, since this methos is only called when the component is about to be unmounted).
EDIT:
Look at the Movie
component. Using localStorage
as our store, in the getMovieById
method, instead of:
getMoviebyId = (id) => {
requestApi.fetchDataById('movie', id).then(response => {
//this next sentence will call DiscoveryPage's setState
//DiscoveryPage is not mounted, so a warning will be raised
this.props.data(response.data);
});
};
you could have something like:
getMoviebyId = (id) => {
requestApi.fetchDataById('movie', id).then(response => {
//any component can access this store
localStorage.setItem("data", response.data);
});
};
and then, in some other component, or method, access it like so:
localStorage.getItem("data");
Your code is a little bit confusing. I'm having a little trouble understanding where you're using the DiscoveryPage
's data
, the one you're setting when you call the data
method. But anyway, hope this helps.
Just one more side note: When using localStorage
, it's good practice to manage it, by which I mean calling localStorage.removeItem("data")
when you're done using the data
value. This is best done, for example, in some componentWillUnmount
method.