Wait for API call data before render react hooks
Asked Answered
C

4

11

I make an API call.

It appears React goes ahead to build a table without the data, thus throwing error of

Uncaught TypeError: Cannot read property 'map' of undefined

Here's what I'm doing

useEffect() pretty straightforward

  const [data, setData] = useState();
  const [isBusy, setBusy] = useState()

  useEffect(() => {
    setBusy(true);
    async function fetchData() {
      const url = `${
        process.env.REACT_APP_API_BASE
        }/api/v1/endpoint/`;

      axios.get(url).then((response: any) => {
        setBusy(false);
        setData(response.data.results)
        console.log(response.data.results);
      });
    }

    fetchData();
  }, [])

Then I'm trying to render a table using the data from the API call above (as and when it becomes available)

            <div className="col-md-12 mt-5">
              {isBusy ? (
                <Loader />
              ) : (
                  <table className="table table-hover">
                    <thead>
                      <tr>
                        <th scope="col">Pharmacy User Full Name</th>
                        <th scope="col">Tests This Month</th>
                        <th scope="col">Tests This Week</th>
                        <th scope="col">Last Test Date</th>
                      </tr>
                    </thead>
                    <tbody>
                      {data.map((item: any, index: any) => {
                        return (<tr>
                          <th scope="row" key={index}>{item.name}</th>
                          <td>Mark</td>
                          <td>Otto</td>
                          <td>@mdo</td>
                        </tr>
                        )
                      })}

                    </tbody>
                  </table>
                )}
            </div>

The above appears intuitive enough for me. So not sure what I need to do. Thanks.

Clink answered 20/11, 2019 at 14:35 Comment(3)
What is your console.log showing?Diannadianne
@Diannadianne Uncaught (in promise) TypeError: Cannot read property 'map' of undefinedClink
I think @Vencovsky solution should work for you. Remember that you might need to parse the results received first with .json()Diannadianne
L
10

You should set isBusy to true in the useState initial value

//                            initial value
const [isBusy, setBusy] = useState(true)

And also check data before data.map

// checking data
{data && data.map((item: any, index: any) => {
    return (<tr>
      <th scope="row" key={index}>{item.name}</th>
      <td>Mark</td>
      <td>Otto</td>
      <td>@mdo</td>
    </tr>
    )
})}
Lichen answered 20/11, 2019 at 14:40 Comment(4)
Combining both now. The checking data part too very important. Thanks for pointing that out too. Will keep in mindClink
@Lichen - Can you pls explain why Do we check for data before data.map? Is it to make sure if data is present or not?Hirsutism
@lAaravl is because data could be undefined and if you do data.map without checking if data is truthy, you might get an error saying cannot read property map of undefined. You also might ask "How will data be undefined?" and the reason why it can be undefined is that the data` state is initialized as undefined in const [isBusy, setBusy] = useState() (empty parameter in useState is passing undefined as the initial state.Lichen
@lAaravl a much better check would be Array.isArray(data) && data.map(...) so it would only work if data is actually an array, but in this case, only doing data && data.map(...) will work also.Lichen
V
3

useEffect will only set isBusy to true after the component tries to render(too late). Remember that useEffect only runs after the browser finishes painting. So the first assertion of isBusy is undefined which evaluates to false

Define true as initial state of isBusy

const [isBusy, setBusy] = useState(true)

Or check for the existence of data instead of isBusy

Valuer answered 20/11, 2019 at 14:38 Comment(1)
What if we're not using useState but instead useEffect and waiting for an API call to finish before rendering?Creaky
W
2

Your setBusy(true); happens inside the useEffect. The useEffect will execute after your first render so this is too late.

I recommend setting isBusy to true by default via the parameter of useState:

const [isBusy, setBusy] = useState(true);

You then don't need to set it to true inside the useEffect anymore.

Wentletrap answered 20/11, 2019 at 14:39 Comment(0)
A
0

You can remove the isBusy state variable entirely as it just replicates data. Unless you later decide to do something fancy like 'load more data', then I think it just complicates things.

<div className="col-md-12 mt-5">
  {!data? (
    <Loader />
  ) : (
     <table className="table table-hover">
        <thead>
          <tr>
            <th scope="col">Pharmacy User Full Name</th>
            <th scope="col">Tests This Month</th>
            <th scope="col">Tests This Week</th>
            <th scope="col">Last Test Date</th>
          </tr>
        </thead>
        <tbody>
          {data.map((item: any, index: any) => {
            return (<tr>
             <th scope="row" key={index}>{item.name}</th>
              <td>Mark</td>
              <td>Otto</td>
              <td>@mdo</td>
            </tr>
           )
          })}
       </tbody>
     </table>
   )}
</div>

In the above, if there is no data !data then it will show the loader. Once data gets populated it will render your table with the loaded data.

Hope this helps.

Agnes answered 8/6, 2023 at 13:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.