React componentDidMount vs useEffect hooks for API call - reactjs

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>
);
}
}

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>
);
}

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);

Related

fetching data not showing in table in react

I am create a table and fetching data using axios but in the table I am not able to print the data when I check data is printing in browser but not able to print the particular data to a table format so what should be change in my code?
import { useEffect, useState } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import { Table } from "react-bootstrap";
import axios from "axios";
export default function App() {
const [user, setUser] = useState([]);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users", (req, res) => {
res.json();
})
.then((data) => setUser({ ...user, data }))
.catch((error) => console.error(error));
});
return (
<div className="App">
<h3 className="text-primary">User List</h3>
<Table
variant="danger"
striped
bordered
hover
className="shadow-lg text-center"
>
<thead>
<tr>
<th>id</th>
<th>Name</th>
<th>UserName</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{user?.data?.length > 0 &&
user.data.map((user) => {
return (
<tr key={user.id}>
<td>{JSON.stringify(user.data["data"].id)}</td>
<td>{JSON.stringify(user.data["data"].name)}</td>
<td>{JSON.stringify(user.data["data"].username)}</td>
<td>{JSON.stringify(user.data["data"].email)}</td>
</tr>
);
})}
</tbody>
</Table>
{/* <div>{JSON.stringify(user.data["data"])}</div> */}
</div>
);
}
for example
import { useEffect, useState } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import { Table } from "react-bootstrap";
import axios from "axios";
export default function App() {
const [user, setUser] = useState([]);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((res) => {
setUser(res.data);
})
.catch((error) => console.error(error));
}, []);
return (
<div className="App">
<h3 className="text-primary">User List</h3>
<Table
variant="danger"
striped
bordered
hover
className="shadow-lg text-center"
>
<thead>
<tr>
<th>id</th>
<th>Name</th>
<th>UserName</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{user?.length > 0 &&
user.map((userData) => {
return (
<tr key={userData.id}>
<td>{userData.id}</td>
<td>{userData.name}</td>
<td>{userData.username}</td>
<td>{userData.email}</td>
</tr>
);
})}
</tbody>
</Table>
{/* <div>{JSON.stringify(user)}</div> */}
</div>
);
}
Replace the useEffect code as follow.
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((data) => setUser({ ...user, data }))
.catch((error) => console.error(error));
}, []);
You already know that calling this api will give you an array of users so you can initialise the state as empty array as:
const [users, setUsers] = useState([]);
and when you are using axios then you don't have to use res.json(). axios will do it for you out of the box.
axios
.get("https://jsonplaceholder.typicode.com/users")
.then(({ data }) => setUsers(data))
.catch((error) => console.error(error));
so, after getting data using get method of axios it will return you a promise and you can get data from its data property that is passed an first args. You can directly set state which will be an array of objects.
.then(({ data }) => setUsers(data))
Here I've destructed the object to get only the data property.
Since users will be an array of objects, so you don't have to do any check. You can directly use user.id to get the respective property.
Codesandbox link
export default function App() {
const [users, setUsers] = useState([]);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then(({ data }) => setUsers(data))
.catch((error) => console.error(error));
}, []);
return (
<div className="App">
<h3 className="text-primary">User List</h3>
<Table
variant="danger"
striped
bordered
hover
className="shadow-lg text-center"
>
<thead>
<tr>
<th>id</th>
<th>Name</th>
<th>UserName</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map((user) => {
return (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.username}</td>
<td>{user.email}</td>
</tr>
);
})}
</tbody>
</Table>
{/* <div>{JSON.stringify(user.data["data"])}</div> */}
</div>
);
}

How can i change this code to react redux

Here i want to change this code to react redux. How i can change this code using react redux. Kindly provide any solutions for changing this code to react redux using GET method api. As iam new to react js how can i change this code using react redux.
import React from "react";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
export default function User() {
const [users, setUsers] = useState([]);
const f = async () => {
const res = await fetch("https://reqres.in/api/userspage=1");
const json = await res.json();
setUsers(json.data);
};
useEffect(() => {
f();
}, []);
const handleLogout = (e) => {
localStorage.clear();
window.location.pathname = "/";
}
return (
<div>
<h1>List Users</h1>
<div>
<button onClick={handleLogout}>Logout</button>
<nav>
<Link to="/Home">Home</Link>
</nav>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>First_name</th>
<th>Last_name</th>
<th>Email</th>
<th>Avatar</th>
</tr>
</thead>
<tbody>
{users.length &&
users.map((user) => {
return (
<tr>
<td> {user.id}</td>
<td>{user.first_name}</td>
<td> {user.last_name} </td>
<td>{user.email}</td>
<td> <img key={user.avatar} src={user.avatar} alt="avatar" /></td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}

TypeError: data.map is not a function i am getting an error while api call and an error called id is not defined

i want to navigate to other page while clicking on the button and while clicking the button i also have to call an api.but its not working can anyone tell me whats the problem
[its the api link] [1]: https://api.tvmaze.com/shows/$%7Bid%7D its my contact component
import React,{useState,useEffect} from 'react';
import axios from 'axios';
export const Contact = () => {
const url = `https://api.tvmaze.com/shows/${id}`;
const [data, setData] = useState([]);
useEffect(() => {
axios.get(url).then((json) => setData(json.data));
}, []);
const renderTable = () => {
return data.map((user, i) => {
return (
<tr key={i}>
<td>{user.show?.name}</td>
<td>{user.show?.language}</td>
<td>{user.show?.genres}</td>
<td>{user.show?.runtime}</td>
<td>{user.show?.premiered}</td>
<td>{user.show?.rating?.average}</td>
<td>{user.show?.network?.country?.name}</td>
<td>
<img src={user?.show?.image?.medium} alt="poster" />
</td>
</tr>
);
});
};
return (
<div>
<table className=" table table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">language</th>
<th scope="col">genres</th>
<th scope="col">runtime</th>
<th scope="col">premiered</th>
<th scope="col">Rating</th>
<th scope="col">country name</th>
<th scope="col">image</th>
</tr>
</thead>
<tbody>{renderTable()}</tbody>
</table>
</div>
);
};
Here You can use this , No Error will occured
const renderTable = () => {
return data && data.length > 0 && data.map((user, i) => {
return (
<tr key={i}>
<td>{user.show?.name}</td>
<td>{user.show?.language}</td>
<td>{user.show?.genres}</td>
<td>{user.show?.runtime}</td>
<td>{user.show?.premiered}</td>
<td>{user.show?.rating?.average}</td>
<td>{user.show?.network?.country?.name}</td>
<td>
<img src={user?.show?.image?.medium} alt="poster" />
</td>
</tr>
);
});
};
I think the problem is in api and also the id here
const url = `https://api.tvmaze.com/shows/${id}
where have you declared the id ? and to make sure that the data is coming fine before using it first console.log() the response data and see what you get and open the network tab to see the reason of the error clearly

How to pass an array as a prop and render in child component in typescript react hooks

I have difficulty passing an array as a prop to a component from the parent and rendering the same in react typescript.
Here is the parent component where the array is been passed.
import ReportComponent from '../Components/Reports/ReportComponent';
import { ReportData } from "../Types/ReportData.types";
const Report = () => {
const [Report, setReport] = useState<ReportData[]>([]);
ReportService.GetReport()
.then((response) => {
console.log(response.data.data);
setReport(response.data.data);
toast.success(response.data.message);
}).catch((e) => {
console.log(e);
});
return <ReportComponent report {...Report}/>;
But I discovered that the array is not getting to the child and I am getting is
TypeError: Report.map is not a function
import { ReportData } from "../../Types/Report.types";
const ReportComponent = (props:ReportData) => {
console.log("props",props)
const [Report, setReport] = useState<ReportData[]>([]);
setReport(props)
return <div className="row">
<div className="table-responsive">
{ Report.map((report)=>(
<table className="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">UID</th>
<th scope="col">Value</th>
<th scope="col">Length</th>
</tr>
</thead>
<tbody className="table table-striped table-sm">
<tr>
<td>{report.name}</td>
<td>{report.UID}</td>
<td>{report.Value}</td>
<td>{report.Length}</td>
</tr>
</tbody>
</table>
))}
</div>
</div>
}
TL;DR:
const Report = () => {
const [report, setReport] = useState<ReportData[]>([]);
useEffect(() => {
ReportService.GetReport()
.then((response) => {
console.log(response.data.data);
setReport(response.data.data);
toast.success(response.data.message);
}).catch((e) => {
console.log(e);
});
}, []);
return <ReportComponent reports={report} />;
);
interface ReportComponentProps {
reports: ReportData[];
}
const ReportComponent = ({
reports,
}: ReportData) => {
return (
<div className="row">
<div className="table-responsive">
{reports.map((report) => (
<table className="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">UID</th>
<th scope="col">Value</th>
<th scope="col">Length</th>
</tr>
</thead>
<tbody className="table table-striped table-sm">
<tr>
<td>{report.name}</td>
<td>{report.UID}</td>
<td>{report.Value}</td>
<td>{report.Length}</td>
</tr>
</tbody>
</table>
))}
</div>
</div>
);
}
You should not do things as fetching data (ReportService.GetReport()...) inside the render of a component. If you do, every time a component re-renders, that code is executed again, meaning a new fetch will happen.
Passing props is done like <YourComponent propA={propValue} />
Your props do not have the type of ReportData (const ReportComponent = (props:ReportData) => {. props is an object with attributes with the names of the actual props.
You should not do setState inside the render. Just like the fetch, every time the component re-renders, that code is executed again. Because a setState causes a re-render, that means that the "render code" is executed again, so another setState is executed, that causes another re-render, and so on.
If you recive props by properties, you do not need (and should not) do a setState(props). It is not only redundant, but also causes performance losses.
There are a few other issues with your code. I encourage you to go through the baiscs of react again.

How can I pass props to another components with in reactjs

I'm trying to pass product data from AllProducts component to Product component.
AllProducts.jsx: is showing all the products I have and Product.jsx will show specific product and how can I pass data to Product.jsx?
Here is my AllProducts.jsx:
const AllProducts = (props) => {
const [products, setProducts] = useState([]);
const getProductsAPI = () => {
axios
.get("http://localhost:8000/api/products")
.then((res) => {
setProducts(res.data);
getProductsAPI();
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getProductsAPI();
}, [props]);
return (
<div>
<table className="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{products.map((product, i) => (
<tr key={i}>
<th scope="row">{i}</th>
<td>{product.title}</td>
<td>
<Link to={`/products/${product._id}`}> View </Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
and here is my Product.jsx:
const Product = (props) => {
return (
<div className="container">
<h4>{props.product.title}</h4>
</div>
);
};
export default Product;
Here is my project github if you want to look at all the code I have: https://github.com/nathannewyen/full-mern/tree/master/product-manager
If the data is fully loaded for each product in AllProducts, and you don't want to make another API call by product id in the Product component, in this case, you don't have to use a route link to view Product, just make a conditional rendering to show Product component inside AllProducts component. pseudo-code as below,
const [showProduct, setShowProduct] = useState(false);
const [currentProduct, setCurrentProduct] = useState();
const showProduct = (product) => {
setShowProduct(true);
setCurrentProduct(product);
}
<tbody>
{products.map((product, i) => (
<tr key={i}>
<th scope="row">{i}</th>
<td>{product.title}</td>
<td>
<button type="button" onclick = {showProduct(product)}>View</button>
</td>
</tr>
))}
</tbody>
return (showProduct ? <Product /> : <AllProucts/>)
If you also need to make another API call to get extra data for each product, then use the router link but perhaps you can not pass props.

Resources