Reactjs async rendering of components

JavascriptAjaxAsynchronousReactjsJquery Deferred

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

The basic example of async rendering of components is below:

import React				from 'react';
import ReactDOM				from 'react-dom';		 
import PropTypes			from 'prop-types';

export default class YourComponent extends React.PureComponent {
	constructor(props){
		super(props);
		this.state = {
			data: null
		}		
	}

	componentDidMount(){
		const data = {
				optPost: 'userToStat01',
				message: 'We make a research of fetch'
			};
		const endpoint = 'http://example.com/api/phpGetPost.php';		
		const setState = this.setState.bind(this);		
		fetch(endpoint, {
			method: 'POST',
			body: JSON.stringify(data)
		})
		.then((resp) => resp.json())
		.then(function(response) {
			setState({data: response.message});
		});
	}
		
	render(){
		return (<div>
			{this.state.data === null ? 
				<div>Loading</div>
			:
				<div>{this.state.data}</div>
			}
		</div>);
	}
}

Solution 3 - Javascript

Async state management (Playground)

The following solution allows for async state management and can be used for HTTP related requirements if implemented correctly.

Requirements

  • Only re-render elements consuming the observable.
  • Automatically subscribe and unsubscribe from the observable.
  • Support multiple and joint observables.
  • Provide a loading state
  • Simple and easy implementation

Expected behaviour

return (
    <Async select={[names$]}>
        {result => <div>{result}</div>}
    </Async>
);

The provided example above will subscribe to the observable names$. The content/children of the Async component will re-render when next is fired on the observable, not causing the current component to re-render.

Async Component

export type AsyncProps<T extends any[]> = { select: { [K in keyof T]: Observable<T[K]> }, children: (result?: any[]) => JSX.Element };
export type AsyncState = { result?: any[] };

export default class Async<T extends any[]> extends Component<AsyncProps<T>, AsyncState> {

    private subscription!: Subscription;

    constructor(props: AsyncProps<T>) {
        super(props);
        this.state = {};
    }

    componentDidMount() {
        this.subscription = combineLatest(this.props.select)
            .subscribe(result => this.setState({ result: result as T }))
    }

    componentWillUnmount() {
        this.subscription.unsubscribe();
    }

    render() {
        return (
            <Fragment>
                {this.props.children(this.state.result)}
            </Fragment>
        );
    }

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontunaView Question on Stackoverflow
Solution 1 - JavascriptMichelle TilleyView Answer on Stackoverflow
Solution 2 - JavascriptRomanView Answer on Stackoverflow
Solution 3 - JavascriptRudi Jansen van VuurenView Answer on Stackoverflow