How can I make an axios get request wait before rendering my react component

Viewed 2880

I have the following code:

import React from "react";
import TextField from "@material-ui/core/TextField";
import Autocomplete from "@material-ui/lab/Autocomplete";
import axios from "axios";

var config = {
  headers: {
    Accept: "application/json; odata=verbose",
    Authorization: "Bearer " + access_token,
  },
};

class Searchbar extends React.Component {
   async componentWillMount() {
    await axios.get(contracts, config).then((res) => {
      this.setState({ allContracts: res.data });
    });
  }
  render() {

    return (
      <div className="searchbar_wrapper">
        <Autocomplete
          freeSolo
          id="contract-search-bar"
          disableClearable
          options={contracts.map((option) => option.name)}
          onChange={(event, value) => {
          let selectedContractID =(value)
          }}
          renderInput={(params) => (
            <TextField
              {...params}
              label="Search"
              margin="normal"
              variant="outlined"
              InputProps={{ ...params.InputProps, type: "search" }}
            />
          )}
        />
      </div>
    );
  }
}
export default Searchbar;

I am using component will mount as I want this function to first make a call before rendering my component... the axios call will get a result like below:

    results = [
      { name: "aaa" },
      { name: "bb" },
      { name: "cccc" },
    ];

The result would be over 500 entries..

The problem I have is when the component is rendering it keeps saying:

Cannot read property 'allContracts' of null

And the reason for this is because the axios function has not finished getting its results...

Please let me know how I can delay rendering and wait for axios to first get results and then continue

4 Answers

You can basically hold a "loading" state, and while the loading is true, render a spinner or something that the component shows.

This will help. No need for ComponentWillMount. Also there is a need for error-handling.

import React, { Component } from "react";
import axios from "axios";

class Test extends Component {
  state = {
    allContracts: null,
    error: false,
    loading: false,
  };

  async getAllContracts() {
    setLoading(true);
    try {
      const response = await axios.get(contracts, config);
      this.setState({ allContracts: response.data });
    } catch (error) {
      this.setState({ error: true });
    } finally {
      setLoading(false);
    }
  }

  componentDidMount() {
    this.getAllContracts();
  }

  render() {
    const { error, loading, allContracts } = this.state;

    if (loading) {
      return <div className="spinner"></div>; // add a spinner or something until the posts are loaded
    }

    if (error) {
      return <div className="error">Something went wrong</div>;
    }

    return allContracts.map((el, index) => {
      return <div key={index}>{...}</div>; // whatever you want to render
    });
  }
}

export default Test;

You can do it like @pirhan mentioned:

    constructor() {
        this.state = {
            isLoaded: false
        }
    }

    async componentDidMount() {
        const res = await axios.get(contracts, config);
        this.setState({ allContracts: res.data, isLoaded: true });
    }

    render() {
        if (!this.state.isLoaded) {
            return null /* or a loader/spinner */
        }

        return "I'm loaded ok."
    }

Manage a loading state as well as define your state like this:

import React from "react";
    import TextField from "@material-ui/core/TextField";
    import Autocomplete from "@material-ui/lab/Autocomplete";
    import axios from "axios";

    var config = {
      headers: {
        Accept: "application/json; odata=verbose",
        Authorization: "Bearer " + access_token,
      },
    };

    class Searchbar extends React.Component {

       state = {   //define state here
          allContracts: []
        }
       async componentDidMount() {
        await axios.get(contracts, config).then((res) => {
          this.setState({ allContracts: res.data });
        });
      }
      render() {

        return (
          <div className="searchbar_wrapper">
          {   allContracts.length > 0 ?  //wait for data - manage loading state
            <Autocomplete
              freeSolo
              id="contract-search-bar"
              disableClearable
              options={allContracts.map((option) => option.name)}
              onChange={(event, value) => {
              let selectedContractID =(value)
              }}
              renderInput={(params) => (
                <TextField
                  {...params}
                  label="Search"
                  margin="normal"
                  variant="outlined"
                  InputProps={{ ...params.InputProps, type: "search" }}
                />
              )}
            />
          : <h1>Loading.. </h1>
          </div>
        );
      }
    }
    export default Searchbar;
Related