admin管理员组

文章数量:1330564

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 initially null 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
Add a ment  | 

4 Answers 4

Reset to default 4

Set 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.

本文标签: javascriptHow to delay page rendering until data received from APIStack Overflow