12

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.

3
  • What is your console.log showing? Commented Nov 20, 2019 at 14:39
  • @Konstantin Uncaught (in promise) TypeError: Cannot read property 'map' of undefined Commented Nov 20, 2019 at 14:40
  • I think @Vencovsky solution should work for you. Remember that you might need to parse the results received first with .json() Commented Nov 20, 2019 at 14:42

4 Answers 4

11

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>
    )
})}
Sign up to request clarification or add additional context in comments.

4 Comments

Combining both now. The checking data part too very important. Thanks for pointing that out too. Will keep in mind
@Vencovsky - Can you pls explain why Do we check for data before data.map? Is it to make sure if data is present or not?
@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.
@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.
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

1 Comment

What if we're not using useState but instead useEffect and waiting for an API call to finish before rendering?
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.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.