0

When I am trying to make an API call using in useEffect hook (before the component did mount), somehow the state is not getting updated, hence I am getting an error Cannot read property of undefined.

But if I am converting the same logic to a Class component and making the API call in the componentDidMount function, the code works well.

Could anyone tell me why?

Using useEffect

import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";

export default function Customers() {
  const [customers, setCustomers] = useState([]);

  useEffect(() => {
    axios
      .get("http://localhost:5000/customers")
      .then((res) => {
        const data = res.data;
        setCustomers(data);
      })
      .catch((err) => console.log(err));
  }, []);

  useEffect(() => {
    console.log(customers);
  }, [customers]);

  return (
    <div className="container-fluid d-flex flex-column align-items-center justify-content-center">
      <div className="top">Customers</div>
      <div className="tables">
        <table class="table table-striped table-hover">
          <thead>
            <tr>
              <th scope="col">Account No</th>
              <th scope="col">Name</th>
              <th scope="col">E-mail</th>
              <th scope="col">Balance</th>
            </tr>
          </thead>
          <tbody>
            {customers.data.map((customer) => ( // error on this line.
              <tr>
                <th scope="row">{customer.account_no}</th>
                <td>{customer.name}</td>
                <td>{customer.email}</td>
                <td>{customer.balance}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Class Based Component

import React, { Component } from "react";
import axios from "axios";
import "./Customers.css";

export default class Customers extends Component {
  state = {
    customers: [],
  };

  componentDidMount() {
    axios
      .get("http://localhost:5000/customers")
      .then((res) => {
        res.data.sort();
        console.log(res.data);
        this.setState({ customers: res.data });
      })
      .catch((err) => console.log(err));
  }

  render() {
    return (
      <div className="container-fluid main w-75 my-4 d-flex flex-column align-items-center">
        <div className="top p-4 d-flex justify-content-center">
          Our Customers
        </div>
        <div className="tables w-100">
          <table class="table table-striped table-hover">
            <thead>
              <tr>
                <th scope="col">Account No</th>
                <th scope="col">Name</th>
                <th scope="col">E-mail</th>
                <th scope="col">Balance</th>
              </tr>
            </thead>
            <tbody>
              {this.state.customers.map((customer) => (
                <tr>
                  <th scope="row">{customer.account_no}</th>
                  <td>{customer.name}</td>
                  <td>{customer.email}</td>
                  <td>{customer.balance}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    );
  }
}

2 Answers2

3

You are not setting state properly in useEffect hook. instead of setCustomers({data:data}); it should be just setCustomers(data);

useEffect(() => {
    axios
      .get("http://localhost:5000/customers")
      .then((res) => {
        const data = res.data;
        setCustomers(data);
      })
      .catch((err) => console.log(err));
  }, []);

Now because customers is an array, just map over customers instead of customers.data.map.

customers.map((customer)=>{})

So the final code will be

import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";

export default function Customers() {
  const [customers, setCustomers] = useState([]);

  useEffect(() => {
    axios
      .get("http://localhost:5000/customers")
      .then((res) => {
        const data = res.data;
        setCustomers(data);
      })
      .catch((err) => console.log(err));
  }, []);

  useEffect(() => {
    console.log(customers);
  }, [customers]);

  return (
    <div className="container-fluid d-flex flex-column align-items-center justify-content-center">
      <div className="top">Customers</div>
      <div className="tables">
        <table class="table table-striped table-hover">
          <thead>
            <tr>
              <th scope="col">Account No</th>
              <th scope="col">Name</th>
              <th scope="col">E-mail</th>
              <th scope="col">Balance</th>
            </tr>
          </thead>
          <tbody>
            {customers.map((customer) => ( 
              <tr>
                <th scope="row">{customer.account_no}</th>
                <td>{customer.name}</td>
                <td>{customer.email}</td>
                <td>{customer.balance}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}
Gulam Hussain
  • 1,591
  • 10
  • 19
2

You are declaring your customers state as an array:

const [customers, setCustomers] = useState([]);

But you are passing an object after fetching the data:

 setCustomers({ data: data });

That's why your map iteration in the return section fails, because you are setting the state to an object and not an array. If data is an array you should only assign it like this:

setCustomers(data);

The componentDidMount works because you are assigning res.data directly to the customers state and it turns out to be similar to:

setCustomers(data);