web-dev-qa-db-fra.com

Comment attendre AJAX réponse et seulement après que rendre le composant?

J'ai un problème avec l'un de mes composants React. Je pense que AJAX ne charge pas tout le contenu du serveur externe avant que React ne rende le composant ChildComp.

 Obj tree

Ci-dessus, vous pouvez voir l’arbre de réponse provenant du serveur. Et voici le code de mon composant:

var ChildComp = React.createClass({
  getInitialState: function(){
    return {
      response: [{}]
    }
  },
  componentWillReceiveProps: function(nextProps){
    var self = this;

    $.ajax({
      type: 'GET',
      url: '/subscription',
      data: {
        subscriptionId: nextProps.subscriptionId //1
      },
      success: function(data){
        self.setState({
          response: data
        });
        console.log(data);
      }
    });
  },
  render: function(){
      var listSubscriptions = this.state.response.map(function(index){
        return (
          index.id
        )
      });
      return (
        <div>
          {listSubscriptions}
        </div>
      ) 
  }
});

Cela fonctionne très bien, mais si je change mon retour à:

  return (
     index.id + " " + index.order.id
  )

l'identifiant n'est pas défini. Et toutes les autres propriétés de l'objet order. Pourquoi est-ce comme ça? Si je console.log ma réponse après la fonction success, il donne toutes les données (comme dans l'image). Mon hypothèse est que seuls les premiers objets sont chargés lorsque React rend le composant, puis tous les autres objets internes sont chargés. Je ne peux pas vraiment dire si c'est le cas (ça a l'air si bizarre) ni comment résoudre ce problème.

J'ai aussi essayé quelque chose comme ça

  success: function(data){
    self.setState({
      response: data
    }, function(){
      self.forceUpdate();
    })
  }.bind(this)

mais le rendu est toujours effectué avant que ma console.log(data) ne soit déclenchée. Comment attendre la réponse AJAX et uniquement après avoir rendu le composant?

35
Tom Joad

Voici votre code retravaillé avec quelques commentaires des pièces modifiées

  getInitialState: function(){
    return {
      // [{}] is weird, either use undefined (or [] but undefined is better).
      // If you use [], you loose the information of a "pending" request, as 
      // you won't be able to make a distinction between a pending request, 
      // and a response that returns an empty array
      response: undefined
    }
  },

  loadSubscriptionData: function(subscriptionId){
    var self = this;

    // You probably want to redo the ajax request only when the 
    // subscriptionId has changed (I guess?)
    var subscriptionIdHasChanged = 
       (this.props.subscriptionId !== subscriptionId)

    if ( subscriptionIdHasChanged ) {
        // Not required, but can be useful if you want to provide the user
        // immediate feedback and erase the existing content before 
        // the new response has been loaded
        this.setState({response: undefined});

        $.ajax({
          type: 'GET',
          url: '/subscription',
          data: {
            subscriptionId: subscriptionId //1
          },
          success: function(data){

            // Prevent concurrency issues if subscriptionId changes often: 
            // you are only interested in the results of the last ajax    
            // request that was made.
            if ( self.props.subscriptionId === subscriptionId ) {
                self.setState({
                  response: data
                });
            }
          }
        });
     }
  },

  // You want to load subscriptions not only when the component update but also when it gets mounted. 
  componentDidMount: function(){
    this.loadSubscriptionData(this.props.subscriptionId);
  },
  componentWillReceiveProps: function(nextProps){
    this.loadSubscriptionData(nextProps.subscriptionId);
  },

  render: function(){

      // Handle case where the response is not here yet
      if ( !this.state.response ) {
         // Note that you can return false it you want nothing to be put in the dom
         // This is also your chance to render a spinner or something...
         return <div>The responsive it not here yet!</div>
      }

      // Gives you the opportunity to handle the case where the ajax request
      // completed but the result array is empty
      if ( this.state.response.length === 0 ) {
          return <div>No result found for this subscription</div>;
      } 


      // Normal case         
      var listSubscriptions = this.state.response.map(function(index){
        return (
          index.id
        )
      });
      return (
        <div>
          {listSubscriptions}
        </div>
      ) 
  }
28
Sebastien Lorber

Tout d’abord, vous voudriez placer l’appel AJAX dans componentWillMount() ou componentDidMount() (si vous devez effectuer une manipulation DOM).

componentWillReceiveProps() est 

Appelé lorsqu'un composant reçoit de nouveaux accessoires. Cette méthode n'est pas appelée pour le rendu initial.

https://facebook.github.io/react/docs/component-specs.html

Mais même si vous placez l'appel AJAX dans componentWillMount() ou componentDidMount(), render() sera probablement appelé avec une réponse vide avant la fin de l'appel de AJAX.

Ainsi, l'ordre d'appel serait le suivant (j'ai supposé que vous avez mis l'appel AJAX dans componentWillMount():

componentWillMount() (un appel AJAX est déclenché) -> componentDidMount() -> render() (restituez avec une réponse vide; index.order.id provoquera une erreur) -> AJAX appel renvoie et appelle this.setState() -> (d'autres méthodes de cycle de vie composant telles que shouldComponentUpdate() appelé; voir le lien FB ci-dessus pour plus de détails) -> render() est appelé à nouveau.

La solution à votre problème est donc:

1) Déplacement de l’appel AJAX vers componentWillMount() ou componentDidMount()

2) Définissez l’état initial uniquement sur [] (par opposition à [{}]) ou vérifiez si index est vide dans

var listSubscriptions = this.state.response.map(function(index){
  return (
    index.id
  )
});
3
Brian Park

Je l'ai jeté dans un JSBin et je n'arrive pas à reproduire votre problème.

http://jsbin.com/jefufe/edit?js,output

Chaque fois que le bouton est cliqué, il envoie de nouveaux accessoires à <ChildComp/>, qui déclenche ensuite sa componentWillReceiveProps, envoie la demande (fictive) ajax, reçoit la réponse (fictive), définit l'état de la réponse sur la réponse (fictive) et retransforme le composant avec les données correctes affichées.

La seule chose que j'ai ajoutée était un contrôle de index.id == null avant d'essayer d'y accéder, mais je ne pense pas que cela puisse résoudre le problème de l'impossibilité d'accéder à index.order.id.

La seule raison pour laquelle je peux expliquer pourquoi vous ne pouvez pas accéder à index.order.id est peut-être parce que vous avez un code que vous ne nous montrez pas qui modifie la réponse à un moment donné.

N'oubliez pas non plus que les composants de React seront toujours restitués lorsque son état ou ses accessoires ont changé, à moins que vous n'ayez défini le comportement dans shouldComponentUpdate qui indique le contraire. Cela signifie que lorsque vous recevez l'objet entier du serveur et que vous le définissez à un état de votre composant, celui-ci sera rendu à nouveau et vous montrera l'état mis à jour. Il n’existe aucune raison/moyen pour React de "rendre superficiellement" votre objet de réponse, comme vous le suggériez dans votre message original.

Je ne suis pas sûr que cette réponse aide, mais j'espère que vous pourrez consulter le JSBin que j'ai fourni et trouver quelque chose que vous auriez peut-être oublié.

0
Michael Parker

Il suffit de faire l'appel AJAX dans componentDidMount() et dans le rendu, vous pouvez simplement vérifier ...

if(this.state.response === '' || this.state.response === [] || this.state.response === undefined)
return <Loader />
else{

// do your stuff here

}

OU si vous utilisez Rea 16.6 ou plus, vous pouvez simplement utiliser des composants paresseux.

0
Diptanshu Pandey