How to render async content on React?

Viewed 1475

I'm building a SPA based on WP API and want to render both posts and their featured images but they come in separate endpoints.

When rendering, React don't wait the request to resolve and get the error: "Uncaught Invariant Violation: Objects are not valid as a React child (found: [object Promise])."

Totally a beginner about Promise and Async/Await functions. Don't even know if I'm using it correctly.

import React, { Suspense, Component } from "react";
import { FontSizes, FontWeights, PrimaryButton, DefaultButton } from 'office-ui-fabric-react';
import axios from "axios";
import './Home.styl'

class Home extends Component {
    constructor() {
      super();

      this.state = {
        posts: []
      }
    }

    componentWillMount() {

      this.renderPosts();

    }

    renderPosts() {

      axios.get('https://cors-anywhere.herokuapp.com/https://sextou.didiraja.net/wp-json/wp/v2/posts')
      .then((response) => {
        // console.log(response)

        this.setState({
          posts: response.data,
        })
      })
      .catch((error) => console.log(error))

    }

    async getImg(mediaId) {

      const getImg = axios
        .get('https://cors-anywhere.herokuapp.com/https://sextou.didiraja.net/wp-json/wp/v2/media/17')
        .then((response) => {
          return {
            url: response.data.source_url,
            alt: response.data.alt_text,
          }
        })

      const obj = getImg

      return (
        <img src={obj.url} />
      )

    }

    render() {

      const { posts } = this.state

      return (
        <span className="Home-route">

        <h1 style={{textAlign: 'center'}}>Sextou!</h1>

          <div className="events-wrapper">
            {
              posts.map((post, key) => {
                return (
                <div className="event-card" key={key}>

                  <img src={this.getImg()} />

                  <h2
                    className="event-title"
                    style={{ fontSize: FontSizes.size42, fontWeight: FontWeights.semibold }}
                  >
                    {post.title.rendered}
                  </h2>

                  {post.acf.event_date}

                  <span>{post.excerpt.rendered}</span>

                  <a href={post.acf.event_link} target="_blank">
                    <DefaultButton
                      text="Acesse o evento"
                    /> 
                  </a>

                  <a href={post.acf.event_ticket} target="_blank">
                    <PrimaryButton
                      text="Comprar ingressos"
                    /> 
                  </a>

                </div>

                )
              })
            } 
          </div>


        </span>
      );
    }
  }
  export default Home;
1 Answers

You could fetch the images the same way as you fetched the posts:

Include them in your state

this.state = {
  posts: [],
  image: null
};

Call getImage in componentWillMount

componentWillMount() {
  this.getPosts();
  this.getImage();
}

setState when the promise resolves:

.then(response => {
  this.setState(state => ({
    ...state,
    image: {
      url: response.data.source_url,
      alt: response.data.alt_text
    }
  }));
});

Display a loading screen or a spinner until the image loads

render() {

  const { posts, image } = this.state;

  if (!image) {
    return "Loading";
  }

  // ...
}

I would also advise using componentDidMount instead of componentWillMount, because componentWillMount is deprecated and is considered unsafe.

Here's a codesandbox example.

Related