我如何在React的卸载组件中设置状态

时间:2018-06-26 09:23:55

标签: javascript reactjs

我们如何在参数中的componentDidMount钩中设置setState?

我的问题是React的警告通知:

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)

在我的DiscoverPage组件中:

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;

在子组件中(本例为Movies组件):

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;

我该如何解决?谢谢。

5 个答案:

答案 0 :(得分:2)

当路由更改时,然后调用getDataItem方法时,将卸载DiscoverPage组件。这就是为什么react抛出错误。

我建议使用react-router组件方法,而不是渲染并将数据移动到可以由任何组件访问的存储中。请参阅react-router render methods

示例:

<Route path="/user/:username" component={User}/>

答案 1 :(得分:1)

需要做一些更改,您使用的逻辑效率不高
从您的问题中了解到我的想法会很好

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: {}
        };
    }
    getALLDATA() {
        //update your state after your request
        request().then(res => {
            this.setState({
                data: res.data
            });
        });
    }
    componentDidMount() {
        this.getALLDATA();
    }
    getDataItem = () => {
        return this.state.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;

答案 2 :(得分:1)

您收到的警告是告诉您无法在未安装的组件(即当前未渲染的组件)上调用setState。您的其他组件之一(MoviesTvShows等)很可能正在调用您作为data道具传递的方法。呈现这些组件之一时,DiscoveryPage组件未呈现,因此通过getDataItem道具调用data将导致DiscoveryPage的{​​{1}}方法未呈现时调用。为了解决这个问题(假设您要做的是将数据从一个组件传递到另一个组件),我建议也许使用setState来存储一个共享变量(或者,如@Anu所建议的那样,使用其他某种存储方式)。

我不明白您对“在参数中”的含义,但是,回答您的问题,您可以使用任何localStorage方法调用setState(即使在component...中调用毫无意义,因为只有在即将卸载组件时才调用此方法。

编辑: 查看componentWillUnmount组件。在Movie方法中将localStorage用作我们的商店,而不是:

getMovieById

您可能会遇到类似的事情:

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);
    });
};

,然后在其他组件或方法中,按如下方式访问它:

getMoviebyId = (id) => {
    requestApi.fetchDataById('movie', id).then(response => {
        //any component can access this store
        localStorage.setItem("data", response.data);
    });
};

您的代码有点混乱。我在理解您使用localStorage.getItem("data"); 的{​​{1}}的位置时遇到了一些麻烦,这是您在调用DiscoveryPage方法时要设置的位置。但是无论如何,希望这会有所帮助。

还有一个补充说明:使用data时,这是一种很好的管理方式,也就是说,使用完data值后,请调用localStorage。例如,最好使用某些localStorage.removeItem("data")方法。

答案 3 :(得分:0)

我认为您缺少从react-router-dom

导入 BrowserRouter
import { Switch, Route, Redirect, BrowserRouter } from 'react-router-dom'; // Add BrowserRouter

class DiscoverPage extends Component {

...
render() {
    return (
    <BrowserRouter> // Add this 
        <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>
    </BrowserRouter>
    );
}
}

答案 4 :(得分:0)

我们可以在 componentDidMount 中设置状态,例如:

eval

但改为使用 componentDidUpdate

(setq-default compile-command 'my-compile-command-fun)