Reactjs 异步呈现组件

我想在 Ajax 请求完成后呈现我的组件。

下面你可以看到我的代码

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


})


}
});

但是我得到了下面的错误,因为我返回渲染内我的 ajax 请求的 done 方法。

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

是否有办法等待我的 ajax 请求结束后再开始呈现?

80018 次浏览

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

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


}