javascriptajaxasynchronousreactjsjquery-deferred

Reactjs async rendering of components


I want to render my component after my ajax request is done.

Below you can see my code

var CategoriesSetup = React.createClass({

    render: function(){
        var rows = [];
        $.get('http://foobar.io/api/v1/listings/categories/').done(function (data) {
            $.each(data, function(index, element){
                rows.push(<OptionRow obj={element} />);
            });
           return (<Input type='select'>{rows}</Input>)

        })

    }
});

But i get the error below because i am returning render inside the done method of my ajax request.

Uncaught Error: Invariant Violation: CategoriesSetup.render(): A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object.

Is there a way to wait for my ajax request to end before start rendering?


Solution

  • There are two ways to handle this, and which you choose depends on which component should own the data and the loading state.

    1. Move the Ajax request into the parent and conditionally render the component:

      var Parent = React.createClass({
        getInitialState: function() {
          return { data: null };
        },
      
        componentDidMount: function() {
          $.get('http://foobar.io/api/v1/listings/categories/').done(function(data) {
            this.setState({data: data});
          }.bind(this));
        },
      
        render: function() {
          if (this.state.data) {
            return <CategoriesSetup data={this.state.data} />;
          }
      
          return <div>Loading...</div>;
        }
      });
      
    2. Keep the Ajax request in the component and render something else conditionally while it's loading:

      var CategoriesSetup = React.createClass({
        getInitialState: function() {
          return { data: null };
        },
      
        componentDidMount: function() {
          $.get('http://foobar.io/api/v1/listings/categories/').done(function(data) {
            this.setState({data: data});
          }.bind(this));
        },
      
        render: function() {
          if (this.state.data) {
            return <Input type="select">{this.state.data.map(this.renderRow)}</Input>;
          }
      
          return <div>Loading...</div>;
        },
      
        renderRow: function(row) {
          return <OptionRow obj={row} />;
        }
      });