4

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.com/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>
</>
)
                
3
  • retry request by ` .catch(getProductdata) ` 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? 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 Commented Jun 15, 2021 at 4:09

4 Answers 4

4

Set a bit of state and return another component 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.com/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 component until you have the data
if (loading) { 
  return (<div>Replace me with a loading component...</div>)
}
  
return (
  <>
  ...
  </>
)
Sign up to request clarification or add additional context in comments.

Comments

1

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.com/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(......)}
  ...
);

Comments

0

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

Comments

0

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.com/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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.