score:31

Accepted answer

Here's your code reworked with some comments of the modified parts

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

score:0

I threw it into a JSBin and I can't seem to reproduce your problem.

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

Every time the button is clicked, it sends new props to <ChildComp/>, which then triggers its componentWillReceiveProps, sends the (fake) ajax request, receives the (fake) response, sets the response state to the (fake) response, and re-renders the component with the correct data shown.

The only thing I added was a check for index.id == null before trying to access it, but I don't think that does anything to solve the issue you're having about being unable to access index.order.id.

The only reason I can come up with as to why you can't access index.order.id is because perhaps you have some code that you aren't showing us that modifies the response at some point.

Also, remember that React components will always re-render when its state or props have changed, unless you have defined behavior in shouldComponentUpdate that says otherwise. This means that when you receive the whole object from the server and set it to a state in your component, it will re-render and show you the updated state. There is no reason/way for React to "shallowly render" your response object like you were suggesting in your original post.

I'm not sure if this answer helps, but hopefully you can look at the JSBin I provided and find something that you might have overlooked.

score:0

Just do the AJAX call in componentDidMount() and in render you can simply check...

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

// do your stuff here

}

OR if you are using react 16.6 or more, you can simply use lazy components.

score:3

First of all, you would want to put the AJAX call inside either componentWillMount() or componentDidMount() (if you need to do some DOM manipulation).

componentWillReceiveProps() is

Invoked when a component is receiving new props. This method is not called for the initial render.

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

But even if you put the AJAX call inside componentWillMount() or componentDidMount(), render() will probably be called with an empty response before AJAX call finishes.

So the order of call would be like this (I assumed you put the AJAX call inside componentWillMount():

componentWillMount() (AJAX call is triggered) -> componentDidMount() -> render() (render with empty response; so index.order.id will cause an error) -> AJAX call returns and calls this.setState() -> (some other component lifecycle methods such as shouldComponentUpdate() is called; see the FB link above for details) -> render() is called again.

So the solution to your problem is:

1) Moving the AJAX call to either componentWillMount() or componentDidMount()

2) Either set the initial state to just [] (as opposed to [{}]) or check if index is empty in

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

Related Query

More Query from same tag