javascriptjsonreactjsasynchronousecmascript-2017

Use Async/Await with Axios in React.js


Following

How to use async/await with axios in react

I am trying to make a simple get request to my server using Async/Await in a React.js App. The server loads a simple JSON at /data which looks like this

JSON

{
   id: 1,
   name: "Aditya"
}

I am able to get the data to my React App using simple jquery ajax get method. However, I want to make use of axios library and Async/Await to follow ES7 standards. My current code looks like this:

class App extends React.Component{
 async getData(){
     const res = await axios('/data');
     console.log(res.json());
 }
 render(){
     return(
         <div>
             {this.getData()}
         </div>
     );
 }
}

Using this approach I get the following error:

Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

Am I not implementing it correctly?


Solution

  • Two issues jump out:

    1. Your getData never returns anything, so its promise (async functions always return a promise) will be fulfilled with undefined if it doesn't reject

    2. The error message clearly shows you're trying to directly render the promise getData returns, rather than waiting for it to settle and then rendering the fulfillment value

    Addressing #1: getData should return the result of calling json:

    async getData(){
       const res = await axios('/data');
       return await res.json();
    }
    

    Addressig #2: We'd have to see more of your code, but fundamentally, you can't do

    <SomeElement>{getData()}</SomeElement>
    

    ...because that doesn't wait for the resolution. You'd need instead to use getData to set state:

    this.getData().then(data => this.setState({data}))
                  .catch(err => { /*...handle the error...*/});
    

    ...and use that state when rendering:

    <SomeElement>{this.state.data}</SomeElement>
    

    Update: Now that you've shown us your code, you'd need to do something like this:

    class App extends React.Component{
        async getData() {
            const res = await axios('/data');
            return await res.json(); // (Or whatever)
        }
        constructor(...args) {
            super(...args);
            this.state = {data: null};
        }
        componentDidMount() {
            if (!this.state.data) {
                this.getData().then(data => this.setState({data}))
                              .catch(err => { /*...handle the error...*/});
            }
        }
        render() {
            return (
                <div>
                    {this.state.data ? <em>Loading...</em> : this.state.data}
                </div>
            );
        }
    }
    

    Futher update: You've indicated a preference for using await in componentDidMount rather than then and catch. You'd do that by nesting an async IIFE function within it and ensuring that function can't throw. (componentDidMount itself can't be async, nothing will consume that promise.) E.g.:

    class App extends React.Component{
        async getData() {
            const res = await axios('/data');
            return await res.json(); // (Or whatever)
        }
        constructor(...args) {
            super(...args);
            this.state = {data: null};
        }
        componentDidMount() {
            if (!this.state.data) {
                (async () => {
                    try {
                        this.setState({data: await this.getData()});
                    } catch (e) {
                        //...handle the error...
                    }
                })();
            }
        }
        render() {
            return (
                <div>
                    {this.state.data ? <em>Loading...</em> : this.state.data}
                </div>
            );
        }
    }