When the page load for the first time with API request it errors out. but after page load if I put the same code back it works fine. Can someone please help what am I missing here. Or show me the trick to delay the page loading until data loads from API.
import React, { useState, useEffect } from 'react'
export default function ProductPage({ data }) {
const [productData, setProductData] = useState(null)
useEffect(() => {
getProductdata()
}, [])
async function getProductdata(){
const secret = "SECRET"
const request = await fetch(`/${data.productsCsv.id}`, {
headers: {
'Authorization': `Basic ${btoa(secret)}`,
'Accept': 'application/json'
}
}).then((request => request.json()))
.then(data => setProductData(data))
.catch(err=>console.log(err))
}
console.log("pdata",productData) // returns null on initial load and then it filled with data.
return (
<>
<div className="stock mb-4 ">
<p className="tracking-wider mb-2">Size</p>
{productData.variants.map((variant,index)=>{
<p>{variant.stock}</p>
if(variant.stock != 0){
return (
<button className={`p-2 border-gray-200 border mr-2 mb-2 hover:bg-black hover:text-white cursor-pointer focus:border-black ${activeSize === index ? 'bg-black text-white' : null}`} role="button" tabIndex={0}
onClick={() => {toggleSize(index); setSize(size)}}
onKeyDown={() => {toggleSize(index); setSize(size)}} key={index}>{variant.variation[0].option}-{variant.stock}</button>
)
}
else {
return(
<button className={`p-2 border-gray-200 border mr-2 mb-2 ${variant.stock == 0 ?'bg-gray-400 line-through text-red-500': null}`} disabled role="button" tabIndex={0}
onClick={() => {toggleSize(index); setSize(size)}}
onKeyDown={() => {toggleSize(index); setSize(size)}} key={index}>{variant.variation[0].option}-{variant.stock}</button>
)
}
})}
</div>
</>
)
When the page load for the first time with API request it errors out. but after page load if I put the same code back it works fine. Can someone please help what am I missing here. Or show me the trick to delay the page loading until data loads from API.
import React, { useState, useEffect } from 'react'
export default function ProductPage({ data }) {
const [productData, setProductData] = useState(null)
useEffect(() => {
getProductdata()
}, [])
async function getProductdata(){
const secret = "SECRET"
const request = await fetch(`https://app.myapi./api/products/${data.productsCsv.id}`, {
headers: {
'Authorization': `Basic ${btoa(secret)}`,
'Accept': 'application/json'
}
}).then((request => request.json()))
.then(data => setProductData(data))
.catch(err=>console.log(err))
}
console.log("pdata",productData) // returns null on initial load and then it filled with data.
return (
<>
<div className="stock mb-4 ">
<p className="tracking-wider mb-2">Size</p>
{productData.variants.map((variant,index)=>{
<p>{variant.stock}</p>
if(variant.stock != 0){
return (
<button className={`p-2 border-gray-200 border mr-2 mb-2 hover:bg-black hover:text-white cursor-pointer focus:border-black ${activeSize === index ? 'bg-black text-white' : null}`} role="button" tabIndex={0}
onClick={() => {toggleSize(index); setSize(size)}}
onKeyDown={() => {toggleSize(index); setSize(size)}} key={index}>{variant.variation[0].option}-{variant.stock}</button>
)
}
else {
return(
<button className={`p-2 border-gray-200 border mr-2 mb-2 ${variant.stock == 0 ?'bg-gray-400 line-through text-red-500': null}`} disabled role="button" tabIndex={0}
onClick={() => {toggleSize(index); setSize(size)}}
onKeyDown={() => {toggleSize(index); setSize(size)}} key={index}>{variant.variation[0].option}-{variant.stock}</button>
)
}
})}
</div>
</>
)
Share
Improve this question
edited Nov 4, 2023 at 11:00
Brian Tompsett - 汤莱恩
5,89372 gold badges61 silver badges133 bronze badges
asked Jun 15, 2021 at 3:52
vsonivsoni
4971 gold badge7 silver badges23 bronze badges
3
- retry request by ` .catch(getProductdata) ` – Charanjit Singh Commented Jun 15, 2021 at 3:56
-
1
But your
productData
is initiallynull
and will be on any subsequent renders until updated by the GET request. You are also console logging as an unintentional side-effect, so what you see actually logged shouldn't be a true measure of anything. What are you expecting to happen? – Drew Reese Commented Jun 15, 2021 at 4:03 - so my code errored out at "productData.variants" loop saying Can not read property of null. So if I remove the code from my return statement and refresh my page the error is gone and when I add same code in my return statement it works fine as productData is no more null – vsoni Commented Jun 15, 2021 at 4:09
4 Answers
Reset to default 4Set a bit of state and return another ponent until you have your data, it should look something like this:
import React, { useState, useEffect } from 'react'
export default function ProductPage({ data }) {
const [productData, setProductData] = useState(null)
const [loading, setLoading] = useSate(true) // set some state for loading
useEffect(() => {
getProductdata()
}, [])
async function getProductdata(){
const secret = "SECRET"
const request = await fetch(`https://app.myapi./api/products/${data.productsCsv.id}`, {
headers: {
'Authorization': `Basic ${btoa(secret)}`,
'Accept': 'application/json'
}
}).then((request => request.json()))
.then((data) => {
setProductData(data)
setLoading(false) // set Loading to false when you have the data
})
.catch(err=>console.log(err))
}
//use the piece of loading state to return other ponent until you have the data
if (loading) {
return (<div>Replace me with a loading ponent...</div>)
}
return (
<>
...
</>
)
Issue
Your productData
is initially null
and will be on any subsequent renders until updated by the GET request. Attempting to access the productData.variants
throws the error because productData
is null.
Solution
You can use some loading state and conditionally render your UI. Use a null-check/optional chaining operator on the productData
state.
const [productData, setProductData] = useState(null);
const [isLoading, setIsLoading] = useState(true); // <-- loading state
useEffect(() => {
getProductdata();
}, []);
async function getProductdata() {
setIsLoading(true); // <-- ensure loading true
const secret = "SECRET";
const request = await fetch(
`https://app.myapi./api/products/${data.productsCsv.id}`,
{
headers: {
'Authorization': `Basic ${btoa(secret)}`,
'Accept': 'application/json'
}
}
).then((request => request.json()))
.then(data => setProductData(data))
.catch(err => console.log(err))
.finally(() => setIsLoading(false); // <-- clear loading state success or fail
}
if (isLoading) return <div>Loading Data</div>; // <-- render loading UI
return (
...
{productData?.variants?.map(......)}
...
);
You're getting this error because productData.variants
doesn't exist so the map function returns an error.
Add a conditional statement that checks productData
before the map function.
{productData ? (
productData.variants.map((variant,index)=>{
//rest of code
}
) : null}
So if productData
is null
the map function does not execute. This is a Ternary Operator, very useful when writing ReactJS.
You can even add a <p>Loading Data</p>
instead of just null
so the user knows data is loading instead of a blank area:
{productData ? (
productData.variants.map((variant,index)=>{
//rest of code
}
) : (
<p>Loading Data...</p>
)}
It's null because it's initialized as null in your useState hook. This is normal.
The useEffect hook should look like this.
useEffect(() => {
function getProductdata() {
const secret = "SECRET"
return fetch(`https://app.myapi./api/products/${data.productsCsv.id}`, {
headers: {
'Authorization': `Basic ${btoa(secret)}`,
'Accept': 'application/json'
}
});
}
getProductdata().then((request => request.json()))
.then(data => setProductData(data))
.catch(err=>console.log(err));
}, []);
You can prevent showing the data by using a logical AND && operator in the template to check if the variable is not null.
{productData && productData.variants.map((variant,index)=> ...
I didn't test this code.
Side note: That SECRET isn't secret. It will appear in the code.