如何延迟页面呈现直到从 api 收到数据

How to delay page rendering until data received from api

第一次使用 API 请求加载页面时出错。但是在页面加载后,如果我放回相同的代码,它就可以正常工作。有人可以帮助我在这里缺少什么吗?或者告诉我延迟页面加载直到数据从 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>
</>
)
                

问题

您的 productData 最初是 null 并将出现在任何后续渲染中,直到被 GET 更新要求。尝试访问 productData.variants 会引发错误,因为 productData 为空。

解决方案

您可以使用一些加载状态并有条件地呈现您的 UI。在 productData 状态上使用 null-check/optional 链接运算符。

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

它为空,因为它在您的 useState 挂钩中被初始化为空。 这是正常的。

useEffect 挂钩应如下所示。

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

}, []);

您可以通过在模板中使用逻辑 AND && 运算符来检查变量是否不为空来防止显示数据。

{productData && productData.variants.map((variant,index)=> ...

我没有测试这段代码。


旁注: That SECRET 不是秘密。它会出现在代码中。

设置一些状态和 return 另一个组件,直到您获得数据,它应该看起来像这样:

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

您收到此错误是因为 productData.variants 不存在,因此地图函数 returns 出错。 在 map 函数前添加检查 productData 的条件语句。

{productData ? (
    productData.variants.map((variant,index)=>{
        //rest of code
    }
) : null}

因此,如果 productDatanull,则地图函数不会执行。这是一篇Ternary Operator,在写ReactJS时很有用

您甚至可以添加 <p>Loading Data</p> 而不仅仅是 null 这样用户就知道正在加载数据而不是空白区域:

{productData ? (
    productData.variants.map((variant,index)=>{
        //rest of code
    }
) : (
    <p>Loading Data...</p>
)}