I am working on react website. I have created one custom data fetching hook 'usePostFetch' as follows:
import React, { useState, useEffect } from "react";
//axios
import axios from "axios";
const usePostFetch = () => {
const [postData, setPostData] = useState([]);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getData = async () => {
setIsLoading(true);
try {
const res = await axios.get("http://localhost:8000/Sell");
const data = await res.data;
setPostData(data);
setIsLoading(false);
} catch (error) {
console.log("Error from fetch: " error);
setError(error.message);
setIsLoading(false);
}
};
getData();
}, []);
const values = [
...new Set(
postData.map((post) => {
return post.productType;
})
),
];
return { postData, values, error, isLoading };
};
export default usePostFetch;
I have a product page that renders when I click any of the links on the home page with a link "/product/:productId".productId is the id of clicked link product.
Product Page:
import React, { useEffect, useState } from "react";
//react router dom
import { useParams } from "react-router";
//Hooks
import usePostFetch from "../../Hooks/usePostFetch";
//styles
import { Wrapper, Info, Discription } from "./Product.styles";
//Server
const Server = "http://localhost:8000";
const Product = () => {
const { productId } = useParams();
const { postData, isLoading, error } = usePostFetch();
const [data, setData] = useState({});
console.log(postData, isLoading, error);
useEffect( () => {
const fetchData = async () => {
var value = await postData.filter((post) => {
return post._id === productId;
});
console.log(value);
setData(value);
};
fetchData();
}, [postData]);
return (
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
</Info>
</Wrapper>
);
};
export default Product;
But when I go to that link I got data in console like this:
Because of these empty arrays, I got errors like this:
What can I do or what is wrong with my code?
CodePudding user response:
It appears you are reading state that doesn't exist yet. The initial data
state is an empty object:
const [data, setData] = useState({});
And on the initial render you are attempting to read from a 0
property, which OFC is undefined still.
data[0] --> OK, undefined
data[0].productName --> NOT OK, throws error trying to access from undefined
You can conditionally render the data
content when you know it's populated:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
{data[0] && (
<img
src={`${Server}/productImages/${data[0].productImage}`}
alt={`${data[0].productName}`}
/>
<div className="data">
<h1>{data[0].productName}</h1>
<h3>{data[0].productPrice}</h3>
</div>
)
</Info>
</Wrapper>
Or you can just use the Optional Chaining operator to defend against null/undefined property accesses:
<Wrapper>
<Info>
{isLoading && <h1> Loading.... </h1>}
{error && <p>ERROR </p>}
{console.log(data)}
<img
src={`${Server}/productImages/${data[0]?.productImage}`}
alt={`${data[0]?.productName}`}
/>
<div className="data">
<h1>{data[0]?.productName}</h1>
<h3>{data[0]?.productPrice}</h3>
</div>
</Info>
</Wrapper>
It also seems that you are really expecting data
to be an array, so you will want your initial state to maintain a state/type invariant, so it should also be declared as an array.
const [data, setData] = useState([]);