如何使用 React 功能组件通过循环回调获取 api

How to fetch api via looped callbacks with React functional components

所以我有一个 40 多个循环调用另一个组件来显示图像。每张图片都有一个 ID,使用该 ID,我可以通过另一个 API 调用获得有关该图片的更多信息,例如名称和描述。

DisplayImage 被调用时,我希望它调用另一个回调函数,该函数将发出 API 调用该图像的元数据,将其存储在一个变量中并显示它作为 H1 标签。

return (
 <div>

  {array.map(index) => {

   // Some Other Code That return a TokenID //

   <>
    {displayImage(tokenId)}
   </>
 
 </div>
})
 const displayImage = (tokenId) => {

  const imageName = GetURI(tokenId)

  return (
      <div className="token-container">
        <h1>{imageName}</h1>
        <img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
      </div>
  )

 }

const GetURI = async (tokenId) => {

    const res = await fetch("https://api"+tokenId , {
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        },
    }).then(data => {
      console.log(data)
      return data.json();
    })
    .then(data => {
      return (data.name || [])
    })
    .catch(err => {
      console.log(err);
    });
}

数据正在控制台上显示,但现在我 运行 陷入了一个我知道 UseEffect 可以解决但我不能完全解决的无限循环问题想办法。我设法通过 UseEffect 使用 [] 属性在控制台上显示数据,但不知道如何显示数据。任何帮助都会很棒。谢谢!

换一种方法可以吗?我会将显示图像放入其自己的组件中。

const DisplayImage = ({tokenId: {_tokenId}}) => {

  const imageName = GetURI(_tokenId)
  
  const GetURI = useCallback(async () => {
      await fetch("https://api"+tokenId , {
            headers: {
              'Content-Type': 'application/json',
              'Accept': 'application/json'
            },
        }).then(data => {
          console.log(data)
          return data.json();
        })
        .then(data => {
          return (data.name || [])
        })
        .catch(err => {
          console.log(err);
        });
      })
    });

    useEffect(() => {
      if (_tokenId) GetURI();
    }, [GetURI]);

  return (
      <div className="token-container">
        <h2>{imageName}</h2>
        <img className="artwork" width="250px" src={`https://ipfs-asdf/${_tokenId}`} />
      </div>
  )
};

然后

return (
 <div>

  {array.map(index) => {

   //Some Other Code//

   <DisplayImage tokenId={tokenId} />
 
 </div>
})

两件事对你的情况有用

  • 在组件外声明的函数不会重新创建每个渲染

  • useState 和 useEffect 配对限制 调用 到 API 仅当 tokenId 更改时[=16] =]

// Put this function outside the component
// so it does not need a useCallback
// i.e not reconstructed each render of DisplayImage

const GetURI = async (tokenId) => {
  ...
});

const DisplayImage = (tokenId) => {

  const [imageName, setImageName] = useState()

  // limit calls to API to when tokenId changes
  // and if eslint complains add GetURI to dependency list
  // - but GetURI never changes, so no un-needed calls from it
  useEffect(() => {
    setImageName(GetURI(tokenId))
  }, [tokenId, GetURI])   
  
  return (
    <div className="token-container">
      <h2>{imageName}</h2>
      <img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
    </div>
  )
};

你也可以抽象到自定义钩子useImageName()

const GetURI = async (tokenId) => {
  ...
});

const useImageName = (tokenId) => {

  const [imageName, setImageName] = useState()

  useEffect(() => {
    setImageName(GetURI(tokenId))
  }, [tokenId, GetURI])   

  return imageName
})
const DisplayImage = (tokenId) => {

  const imageName = useImageName(tokenId)

  return (
    <div className="token-container">
      <h2>{imageName}</h2>
      <img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
    </div>
  )
};

BTW 在 GetURI 这个

return (data.name || [])

看起来应该是

return data.name || ''

您可能应该缓存来自 GetURI(tokenId) 的响应。使用相同的 tokenId 时无需两次询问相同的 URI。 一种简单的方法是使用 react-query:

App.js中的设置:

 // App.js
 import { QueryClient, QueryClientProvider } from 'react-query'

 const queryClient = new QueryClient()

 export default function App() {
   return (
     <QueryClientProvider client={queryClient}>
       <Example />
     </QueryClientProvider>
   )
 }

然后在 DisplayImage 组件中使用(而不是内联函数):

// DisplayImage.js
import { useQuery } from 'react-query'

export function DisplayImage(tokenId) {
  const { isLoading, error, data: imageName } = useQuery(['images', tokenId], GetURI(tokenId))
  return (
    <div className="token-container">
      <h1>{isLoading ? 'loading...' : imageName}</h1>
      <img className="artwork" width="250px" src={`https://ipfs-asdf/${tokenId}`} />
    </div>
  )
}

在大家的帮助下,我找到了解决问题的最佳方法,非常感谢!

我将 GetURI 函数放在显示图像组件中,并在每次有新令牌 ID 时调用 GetURI 的 useEffect 方法,然后我将状态变量设置为返回的任何值。

没有循环,没有错误



const DisplayImage = (data) => {

  const [nftMetadata, setNftMetadata] = useState();

  const GetURI = async (data) => {
     const nftURI = await data.drizzle.contracts.Contract.methods.tokenURI(data.tokenId).call()

      await fetch(nftURI , {
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
          "Access-Control-Allow-Origin": "*"
        },
        })
        .then(data => {
          return data.json();
        })
        .then(data => {
          return setNftMetadata(data || []);
        })
        .catch(err => {
          return console.log(err);
      });
  });

  useEffect(() => {
    GetURI(data);
  }, [data.tokenId])   
  
  return (
    <div className="token-container">

     <h2>{nftMetadata.name}</h2>
     <img className="artwork" width="450px" src={`https://ipfs:/whatever/${nftMetadata.image}`} />

    </div>
  );

};

return (
 <div>

  {array.map(index) => {

   // Some Other Code That returns a TokenID //

   <>
    <DisplayImage address={drizzle.contractList[0].address} tokenId={tokenId} drizzle={drizzle} drizzleState={drizzleState}/>
   </>
 
 </div>
})