反应错误超过最大更新深度

时间:2021-02-11 20:21:16

标签: javascript reactjs

我正在用 React 做我的前几个实验,在这个组件中,我调用一个外部 API 来获取所有 NBA 球员的列表,通过作为组件道具接收的 teamId 过滤他们,最后呈现标记过滤后的玩家。

一个考虑因素是,由于我调用了 API 并获得了一个大列表,所以我将它保持在组件的状态中,以便对该组件的新调用将使用该状态,而不是再次调用 API。这不是生产代码,我不拥有 API,所以我这样做是因为我收到了“请求太多”的消息,因为我一直在尝试一些东西。

无论如何,当我尝试这段代码时,我得到了已经很有名的:

<块引用>

已超出最大更新深度。这可能发生在组件 在 componentWillUpdate 内重复调用 setState 或 组件DidUpdate。 React 将嵌套更新的数量限制为 防止无限循环。

我查看了标记,我认为我没有进行任何会导致渲染方法再次触发的方法调用等等,所以我不知道为什么会发生这种情况。

>

在此先感谢您的帮助。

这是有问题的代码:

 class Players extends Component {
    nbaPlayersUrl = "https://someUrl.com";
    state = {
        players: null,
        selectedTeamPlayers: null
    };

    render() {
        if (this.props.teamId === null) return null;

        if (this.state.players !== null) {
            var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
            var markup = this.getMarkup(selectedTeamPlayers);
            this.setState({selectedTeamPlayers: markup});
        } else {
            this.getPlayersList();
        }

        return (
            this.state.selectedTeamPlayers
        );
    }

    getPlayersList() {
        let api = new ExternalApi();
        let that = this;

        api.get(this.nbaPlayersUrl).then(r => {
            r.json().then(result => {
                let players = result.data.map(p => ({
                    id: p.id,
                    firstName: p.first_name,
                    lastName: p.last_name,
                    position: p.position,
                    heightInches: p.height_inches,
                    heightFeet: p.height_feet,
                    weightPounds: p.weight_pounds,
                    teamId: p.team.id
                }));

                that.setState({players: players});
                var selectedTeamPlayers = that.filterPlayersByTeamId(players);
                var markup = that.getMarkup(selectedTeamPlayers);
                that.setState({selectedTeamPlayers: markup});
            });
        });
    }

    filterPlayersByTeamId(players) {
        return players.filter(p => {
            return p.teamId === this.props.teamId;
        });
    }

    getMarkup(players) {
        var result = players.map(p => {
            <li key={p.id}>
                <div>
                    <label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
                </div>
                <div>
                    <label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
                </div>
                <div>
                    <label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
                </div>
            </li>
        });

        return (
            <ul>
                {result}
            </ul>
        );
    }
}

export default Players;

3 个答案:

答案 0 :(得分:2)

@Sergio Romero - 你不能在渲染函数中设置状态,因为设置的状态会调用一个新的渲染,它会再次设置状态并调用一个新的渲染,并产生一个无限循环。您的数据加载处于渲染和设置状态,这也会造成无限循环。您需要完全重写您的渲染,使其仅成为状态和道具的视图(它不应该操作或加载数据)。我想你想要的,更像是这样的:

class Players extends Component {
    nbaPlayersUrl = "https://someUrl.com";
    static propTypes = {
        teamId: PropTypes.number
    };
    static defaultProps = {
        teamId: null
    };
    constructor(props) {
        super(props);
        this.state = {
            players: null
        };
    }
    componentDidMount() {
        this.getPlayerList();
    }
    filterPlayersByTeamId(players, teamId) {
        return players.filter(p => {
            return p.teamId === teamId;
        });
    }
    getPlayersList = () => {
        let api = new ExternalApi();

        api.get(this.nbaPlayersUrl).then(r => {
            r.json().then(result => {
                let players = result.data.map(p => ({
                    id: p.id,
                    firstName: p.first_name,
                    lastName: p.last_name,
                    position: p.position,
                    heightInches: p.height_inches,
                    heightFeet: p.height_feet,
                    weightPounds: p.weight_pounds,
                    teamId: p.team.id
                }));

                this.setState({players});
            });
        });
    };
    render() {
        if (!this.props.teamId || !this.state.players) return null;

        const selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players, this.props.teamId);

        return (
            <ul>
                {
                    selectedTeamPlayers.map(player => {
                        <li key={player.id}>
                            <div>
                                <label htmlFor="firstName">First Name</label><input type="text" name="firstName" value={player.firstName} readOnly></input>
                            </div>
                            <div>
                                <label htmlFor="lastName">Last Name</label><input type="text" name="lastName" value={player.lastName} readOnly></input>
                            </div>
                            <div>
                                <label htmlFor="position">Position</label><input type="text" name="position" value={player.position} readOnly></input>
                            </div>
                        </li>
                    })
                }
            </ul>
        );
    }
}

export default Players;

答案 1 :(得分:0)

如果 State 和 Props 改变,组件将重新渲染。 在您的 render() 函数中:

 if (this.state.players !== null) {
      var selectedTeamPlayers = this.filterPlayersByTeamId(this.state.players);
      var markup = this.getMarkup(selectedTeamPlayers);
      // this.setState({selectedTeamPlayers: 
 }

尝试更改注释行,每次玩家不为空时,组件状态都会更新,因此组件渲染功能将再次运行。

答案 2 :(得分:0)

由于我们不能在渲染函数内部设置状态,因为它会引起副作用,因此您不能在渲染函数内部调用 getPlayersList()。

@Jason Bellomy 提到的解决方案是通过在 componentDidMount 内部调用 getPlayerList 来解决这个问题的正确方法,因为它在组件插入树后立即调用,因此它是设置渲染页面的初始数据的地方.

class Players extends Component {
    nbaPlayersUrl = "https://someUrl.com";
    state = {
        players: null,
        selectedTeamPlayers: null,
    };
    
    componentDidMount(){
     this.getPlayersList();
    }

    render() {
        if (this.props.teamId === null) return null;

        if (this.state.players !== null && this.state.selectedTeamPlayers !== null) {
             return this.getMarkup(selectedTeamPlayers);
        } else {
           return (<span> Loading ... </span>);
        }
    }

    getPlayersList() {
        let api = new ExternalApi();
        let that = this;

        api.get(this.nbaPlayersUrl).then(r => {
            r.json().then(result => {
                let players = result.data.map(p => ({
                    id: p.id,
                    firstName: p.first_name,
                    lastName: p.last_name,
                    position: p.position,
                    heightInches: p.height_inches,
                    heightFeet: p.height_feet,
                    weightPounds: p.weight_pounds,
                    teamId: p.team.id
                }));
               
                 var selectedTeamPlayers = that.filterPlayersByTeamId(players);
             
                that.setState({
                      players: players, 
                      selectedTeamPlayers: selectedTeamPlayers, 
                });
            });
        });
    }

    filterPlayersByTeamId(players) {
        return players.filter(p => {
            return p.teamId === this.props.teamId;
        });
    }

    getMarkup(players) {
        var result = players.map(p => {
            <li key={p.id}>
                <div>
                    <label htmlFor="firstName">First Name</label> <input type="text" name="firstName" value={p.firstName} readOnly></input>
                </div>
                <div>
                    <label htmlFor="lastName">Last Name</label> <input type="text" name="lastName" value={p.lastName} readOnly></input>
                </div>
                <div>
                    <label htmlFor="position">Position</label> <input type="text" name="position" value={p.position} readOnly></input>
                </div>
            </li>
        });

        return (
            <ul>
                {result}
            </ul>
        );
    }
}

export default Players;