wake-up-neo.net

Warten auf AJAX Antwort und erst danach die Komponente rendern?

Ich habe ein Problem mit einer meiner React-Komponenten. Ich denke, AJAX lädt nicht den gesamten Inhalt von einem externen Server, bevor React die ChildComp-Komponente darstellt.

 Obj tree

Oben sehen Sie den Antwortbaum, der vom Server kommt. Und das ist der Code meiner Komponente:

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

Das funktioniert gut, aber wenn ich meine Rückkehr zu:

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

id ist undefiniert. Und alle anderen Eigenschaften des Auftragsobjekts. Warum ist es so? Wenn ich console.log meine Antwort nach der success-Funktion gibt, werden alle Daten (wie im Bild) angezeigt. Meine Vermutung ist, dass nur die ersten Objekte geladen werden, wenn React die Komponente rendert, und danach alle anderen inneren Objekte geladen werden. Ich kann nicht wirklich sagen, ob das der Fall ist (klingt so komisch), noch kann ich nicht sagen, wie ich das lösen kann.

Ich habe auch so etwas ausprobiert

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

aber es wird immer noch ein Rendering ausgeführt, bevor meine console.log(data) ausgelöst wird. Wie warten Sie auf die Antwort AJAX und erst danach die Komponente rendern?

35
Tom Joad

Hier ist Ihr Code mit einigen Kommentaren der modifizierten Teile überarbeitet

  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

Zunächst möchten Sie den AJAX - Aufruf entweder in componentWillMount() oder componentDidMount() einfügen (wenn Sie DOM-Manipulationen vornehmen müssen).

componentWillReceiveProps() ist 

Wird aufgerufen, wenn eine Komponente neue Requisiten erhält. Diese Methode wird für das anfängliche Rendern nicht aufgerufen.

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

Aber selbst wenn Sie den AJAX - Aufruf in componentWillMount() oder componentDidMount() einfügen, wird render() wahrscheinlich mit einer leeren Antwort aufgerufen, bevor der AJAX - Aufruf beendet wird.

Die Reihenfolge des Aufrufs wäre also so (ich nahm an, dass Sie den AJAX - Aufruf in componentWillMount() gestellt haben:

componentWillMount() (AJAX-Aufruf wird ausgelöst) -> componentDidMount() -> render() (Rendern mit leerer Antwort; daher verursacht index.order.id einen Fehler) -> AJAX ruft zurück und ruft this.setState() -> auf (einige andere Komponentenlebenszyklus-Methoden wie shouldComponentUpdate()) aufgerufen; Details siehe FB-Link) -> render() wird erneut aufgerufen.

Die Lösung für Ihr Problem ist also:

1) Verschieben des AJAX - Aufrufs nach componentWillMount() oder componentDidMount()

2) Setzen Sie den Anfangsstatus entweder auf [] (im Gegensatz zu [{}]) oder prüfen Sie, ob index leer ist

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

Ich habe es in eine JSBin geworfen und ich kann dein Problem nicht reproduzieren.

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

Jedes Mal, wenn die Schaltfläche angeklickt wird, sendet sie neue Requisiten an <ChildComp/>, die dann ihre componentWillReceiveProps auslöst, die (gefälschte) Ajax-Anforderung sendet, die (falsche) Antwort empfängt, den Antwortstatus auf die (falsche) Antwort setzt und erneut rendert die Komponente mit den richtigen Daten angezeigt.

Das einzige, was ich hinzugefügt habe, war ein Check auf index.id == null, bevor ich versuche, darauf zuzugreifen, aber ich denke nicht, dass dies irgendetwas tut, um das Problem zu lösen, dass Sie nicht auf index.order.id zugreifen können.

Der einzige Grund, aus dem ich Ihnen sagen kann, warum Sie nicht auf index.order.id zugreifen können, ist, dass Sie möglicherweise einen Code haben, den Sie uns nicht zeigen, der die Antwort irgendwann ändert.

Denken Sie auch daran, dass React-Komponenten immer dann neu gerendert werden, wenn sich ihr Status oder ihre Requisiten geändert haben, es sei denn, Sie haben in shouldComponentUpdate ein Verhalten definiert, das etwas anderes sagt. Das bedeutet, wenn Sie das gesamte Objekt vom Server erhalten und es in der Komponente auf einen Status setzen, wird es erneut gerendert und zeigt den aktualisierten Status an. Es gibt keinen Grund/keine Möglichkeit für React, Ihr Antwortobjekt "flach zu rendern", wie Sie es in Ihrem ursprünglichen Beitrag vorgeschlagen haben.

Ich bin nicht sicher, ob diese Antwort hilfreich ist, aber hoffentlich können Sie sich die von mir bereitgestellte JSBin ansehen und etwas finden, das Sie möglicherweise übersehen haben.

0
Michael Parker

Führen Sie einfach den Aufruf von AJAX in componentDidMount() aus und in render können Sie einfach überprüfen ...

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

// do your stuff here

}

ODER wenn Sie 16.6 oder mehr verwenden, können Sie einfach Lazy-Komponenten verwenden.

0