Waiting for async function in React component & Showing Spinner

Viewed 10633

Beginner here.

Trying to fetch some data from a server and display it in my react component once its fetched. However, I am having trouble integrating the async function into my react component.

import React, { useState } from "react";
import { request } from "graphql-request";

async function fetchData() {
  const endpoint = "https://localhost:3090/graphql"

  const query = `
    query getItems($id: ID) {
      item(id: $id) {
        title
      }
    }
  `;

  const variables = {
    id: "123123123"
  };

  const data = await request(endpoint, query, variables);
  // console.log(JSON.stringify(data, undefined, 2));

  return data;
}

const TestingGraphQL = () => {
  const data = fetchData().catch((error) => console.error(error));

  return (
    <div>
      {data.item.title}
    </div>
  );
};

export default TestingGraphQL;

I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.

2 Answers

Here you would need to use the useEffect hook to call the API. The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.

Follow along the comments added in between the code below -

CODE

import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "@layouts/default";
import ContentContainer from "@components/ContentContainer";
import { request } from "graphql-request";

async function fetchData() {
   const endpoint = "https://localhost:3090/graphql"

   const query = `
     query getItems($id: ID) {
       item(id: $id) {
         title
       }
     }
   `;

   const variables = {
      id: "123123123"
   };

   const data = await request(endpoint, query, variables);
   // console.log(JSON.stringify(data, undefined, 2));

   return data;
}

const TestingGraphQL = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // useEffect with an empty dependency array works the same way as componentDidMount
  useEffect(async () => {
     try {
       // set loading to true before calling API
       setLoading(true);
       const data = await fetchData();
       setData(data);
       // switch loading to false after fetch is complete
       setLoading(false);
     } catch (error) {
       // add error handling here
       setLoading(false);
       console.log(error);
     }
  }, []);

  // return a Spinner when loading is true
  if(loading) return (
    <span>Loading</span>
  );

  // data will be null when fetch call fails
  if (!data) return (
    <span>Data not available</span>
  );

  // when data is available, title is shown
  return (
    <Layout>
      {data.item.title}
    </Layout>
  );
};

since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.

I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.

See the example below, switched from hooks to a class, but you should be able to make it work! :)

class TestingGraphQL extends Component {
  constructor() {
    super();
    this.state = { data: {}, loading: true};
  }
  
  //when the component is added to the screen. fetch data
  componentDidMount() {
    fetchData()
      .then(json => { this.setState({ data: json, loading: false }) })
      .catch(error => console.error(error));
  }
  
  render() {
     return (
      {this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
    );
  }
};

Related