web-dev-qa-db-fra.com

Pourquoi componentDidUpdate () crée-t-il une boucle infinie?

J'ai stocké url et un token dans state dans Parent composant. Je passe un url et un token comme props du parent Component à l'enfant Component. Cependant, s'il existe un événement dans le parent Component, setState() est déclenché et, par conséquent, componentDidUpdate() de l'enfant Component est exécuté.
Comme componentDidUpdate() provoquait une boucle infinie (car elle déclenche setState () à l'intérieur du composant enfant), j'ai placé la condition. Mais cela n'empêche pas l'erreur.
Composant enfant, c'est-à-dire DisplayRevenue, est le suivant:

import React, { Component } from 'react';
import '../App.css';
import ListData from './listdata.js'
var axios = require('axios');

class DisplayRevenue extends Component {

  constructor(props){
    super(props);
    this.state = { data:[], url:"" }
  console.log(this.props.url);
  }

  componentWillMount() {
    this.loadRevenue(this.props.url, this.props.token);
 }

  componentDidUpdate(){    //creates infinite loop
  //  console.log(this.props.url);
    this.loadRevenue(this.props.url, this.props.token);
  }

  setData(data){
    //if(this.state.url != this.props.url){
    if(this.state.data != data.data){
      console.log(data.data);                     //(1)
  //    console.log(this.state.url);              //(2)
      this.setState(data:data);             
      console.log(this.state.data);               //(3)
  //    console.log(this.props.url);              //(4)
    }     //(1) & (3) yields exactly same value so does (2) & (4)
  }

  loadRevenue(url,token){
    axios({
      method:'get',
      url:url,
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
     .then( (response) => {
    //   console.log(response.data);
       this.setData(response.data);
     })
     .catch(function (error) {
       console.log("Error in loading Revenue "+error);
     });
  }

  render() {
    return (
      <ListData data={this.state.data}/>
    );
  }
};

export default DisplayRevenue;

Le composant parent, c'est-à-dire MonthToDate, est le suivant:

import React, { Component } from 'react';
import '../App.css';
import DisplayRevenue from './displayRevenue'
var axios = require('axios');

class MonthToDate extends Component {

  constructor(props){
    super(props);
    this.state = {
      data:null,
      url:"http://localhost:3000/api/monthtodate"
    }
    //console.log(this.props.location.state.token);
  }

  groupBySelector(event){
    if ((event.target.value)==="invoice"){
      this.setState({url:"http://localhost:3000/api/monthtodate"})
    } else if ((event.target.value)==="customer") {
      this.setState({url:"http://localhost:3000/api/monthtodate?group-by=customerNumber"})
    } else if ((event.target.value)==="month") {
      this.setState({url:"http://localhost:3000/api/invoices?group-by=month"})
    } else {
      this.setState({url:"http://localhost:3000/api/monthtodate"})
    }
    console.log(this.state.url);
  }

  render() {
    return (
      <div>
      <select onChange={(event)=>this.groupBySelector(event)}>
        <option value="invoice">GROUP BY INVOICE</option>
        <option value="customer">GROUP BY CUSTOMER</option>
        <option value="month">GROUP BY MONTH</option>
      </select>
        <DisplayRevenue url={this.state.url} token={this.props.location.state.token}/>
      </div>
    );
  }
}

export default MonthToDate;
  • Qu'est-ce que je rate?
  • De plus, après avoir reçu le url dans le composant enfant, je veux rendre un autre composant basé sur ce url. Par exemple, le composant <ListData /> Ne peut gérer qu'un seul type de url. Comment rendre un autre composant dans render() basé sur le type url ??
10
noobie

Vous appelez un appel ajax dans componentDidUpdate et vous définissez l'état sur le rappel, ce qui déclenchera un autre appel et une mise à jour qui appellera à nouveau la demande ajax et le rappel définira à nouveau l'état, etc.
Votre état dans setData:

if(this.state.data != data.data) 

retournera toujours vrai car les objets sont de type référence et ne peuvent pas être comparés, quelles que soient les données renvoyées par l'appel ajax, ce sera toujours un objet différent et renverra true dans votre condition. Exemple:

var obj1 = {a:1}
var obj2 = {a:1}

console.log(obj1 != obj2); // returns true

Ce que vous pouvez faire, c'est comparer les valeurs primitives à l'intérieur des deux objets.
Par exemple:

if(this.state.data.id != data.id) // id could be a string or a number for example

[~ # ~] modifier [~ # ~]
Une autre chose que j'ai oublié de mentionner qui peut ne pas être directement liée à votre problème mais qui devrait être appliquée, Jamais faire des requêtes ajax dans componentWillMount ou constructor d'ailleurs, car la fonction de rendu sera invoquée avant la fin de votre requête ajax. vous pouvez le lire dans les DOCUMENTS .
Les requêtes Ajax doivent être invoquées dans componentDidMountméthode du cycle de vie à la place.

EDIT # 2
Une autre chose qui peut être utile, dans la fonction de rendu MonthToDate, vous passez une nouvelle instance d'une fonction sur chaque rendu (ce qui peut entraîner un impact sur les performances)

<select onChange={(event)=>this.groupBySelector(event)}>

Essayez de le changer en ceci (l'événement sera transmis automatiquement au gestionnaire):

 <select onChange={this.groupBySelector}>  

Vous devrez également le lier dans le constructeur:

constructor(props){
    super(props);
    this.state = {
      data:null,
      url:"http://localhost:3000/api/monthtodate"
    }
    //console.log(this.props.location.state.token);

    this.groupBySelector = this.groupBySelector.bind(this); // binds this to the class
  }
10
Sagiv b.g