Reactjs async rendering of components
There are two ways to handle this, and which you choose depends on which component should own the data and the loading state.
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>; }});
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} />; }});
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>); }}
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> ); }}