{"content":{"title":"只用 React 和 Chainbase API，如何创建一个自己的 NFT Gallery","body":"**原文作者：[masterdai](https://chainbase.com/blog/article/create-your-own-nft-gallery-with-react-and-chainbase-api)**\r\n\r\n## Introduction\r\n\r\n非同质化代币（NFTs）在数字艺术和收藏品市场引起了革命，标志着我们对数字领域内所有权和价值的感知发生了重大变化。\r\n\r\n本指南的目的是为读者提供有关创建NFT档案的说明，重点关注任务所需的关键技术框架和工具。通过使用Chainbase，一个提供用户友好API端点用于NFT管理的平台，用户可以全面了解如何有效处理NFTs。\r\n\r\n在这个前端[demo](https://nftdemo-lime.vercel.app/)中，我们需要利用两个Chainbase API，并将它们结合起来实现相对复杂的功能。例如，[使用ENS地址](https://docs.chainbase.com/reference/getensreverse)进行NFT查询，以及通过连接您的钱包地址并输入[NFT合约地址](https://docs.chainbase.com/reference/getaccountnfts)进行精确搜索。此外，可以通过调整滚动条来探索多链NFTs。\r\n\r\n\r\n![nftdemo.gif](https://miro.medium.com/v2/resize:fit:1400/1\\*FCt8tJ7zD6F-mq1vXEivcA.gif)\r\n\r\n https://nftdemo-lime.vercel.app/\r\n\r\n## 前期**准备**\r\n\r\n**技术要求：** 要创建一个NFT档案，您将需要一些关键的工具和技术。首要要求是对React的深刻理解，因为它将是我们用于构建应用程序前端的主要库。此外，对Axios和Fetch的了解对于处理HTTP请求至关重要。这些库将使我们能够有效地与API进行交互。\r\n\r\n**设置环境：**\r\n\r\n- **安装Node.js：** 确保您的系统上已安装Node.js，因为它是运行React应用程序所需的。您可以从[Node.js官方网站](https://nodejs.org/)下载它。\r\n- **设置React：** 如果还没有，请使用Create React App设置一个新的React项目，以快速启动。您可以在终端中运行命令 **`npx create-react-app my-nft-profile`** 来完成此操作。\r\n- **Axios和Fetch：** 在项目目录中运行 **`npm install axios`** 安装Axios。Fetch内置在现代浏览器中，因此如果使用Chrome或Firefox等浏览器，则无需额外安装。\r\n- **其他设置：** 确保您的开发环境包括一个代码编辑器（如Visual Studio Code），并熟悉基本的命令行操作，因为它们将在整个开发过程中经常使用。\r\n\r\n有了这些工具和技术的设置，您就可以开始构建您的NFT档案了。\r\n\r\n## **编写NFT演示教程的代码**\r\n\r\n在本节中，我们将详细介绍将编码过程分解为NFT演示的各个部分的步骤，包括每个部分的代码示例和解释。\r\n\r\n### **第1步：`BlockchainSelector` 组件**\r\n\r\n- **目的：** 此组件允许用户选择区块链网络。\r\n- **代码解释**：\r\n    - **自定义钩子 - `useChainId`：** 管理所选区块链网络的状态。\r\n    - **组件设置：** 使用 **`useChainId`** 钩子进行网络选择，并在更改时更新所选的链ID。\r\n\r\n```jsx\r\nimport React, { useState, useEffect, useMemo } from 'react';\r\nimport PropTypes from 'prop-types';\r\nimport styles from '../styles/Home.module.css';\r\n\r\nconst capitalizeFirstLetter = (string) => {\r\n  return string.charAt(0).toUpperCase() + string.slice(1);\r\n}\r\n\r\nconst useChainId = (initialNetwork) => {\r\n  const networkToChainId = useMemo(() => ({\r\n    ethereum: 1,\r\n    polygon: 137,\r\n    bsc: 56,\r\n    avalanche: 43114,\r\n    arbitrum: 42161,\r\n    optimism: 10,\r\n    base: 8453,\r\n    zksync: 324,\r\n  }), []);\r\n\r\n  const [selectedNetwork, setSelectedNetwork] = useState(initialNetwork);\r\n  const selectedChainId = networkToChainId[selectedNetwork];\r\n\r\n  return {\r\n    selectedNetwork,\r\n    setSelectedNetwork,\r\n    selectedChainId,\r\n    networkToChainId \r\n  };\r\n};\r\n\r\nconst BlockchainSelector = ({ setSelectedChainId }) => {\r\n  const { selectedNetwork, setSelectedNetwork, selectedChainId, networkToChainId } = useChainId('ethereum');\r\n\r\n  useEffect(() => {\r\n    setSelectedChainId(selectedChainId);\r\n  }, [selectedChainId]);\r\n\r\n  const handleChange = (event) => {\r\n    setSelectedNetwork(event.target.value);\r\n  };\r\n\r\n  return (\r\n    <div className={styles.blockchainSelectorContainer}>\r\n      <label htmlFor=\"blockchainSelector\" className={styles.blockchainSelectorLabel}>\r\n        Select a Blockchain Network:\r\n      </label>\r\n      <select id=\"blockchainSelector\" value={selectedNetwork} onChange={handleChange}>\r\n        {Object.keys(networkToChainId).map((network) => (\r\n          <option key={network} value={network}>\r\n            {capitalizeFirstLetter(network)}\r\n          </option>\r\n        ))}\r\n      </select>\r\n    </div>\r\n  );\r\n};\r\n\r\nBlockchainSelector.propTypes = {\r\n  setSelectedChainId: PropTypes.func.isRequired,\r\n};\r\n\r\nexport default BlockchainSelector;\r\n```\r\n\r\n### **第2步：`NftCard` 组件**\r\n\r\n- **目的：** 显示单个NFT的详细信息，包括图像和元数据。\r\n- **代码解释**：\r\n    - **实用函数**：\r\n        - **`convertToHttpUrl`**：将IPFS URL转换为显示。\r\n        - **`handleImageError`**：处理图像加载期间的错误。\r\n    - **组件渲染**：显示NFT的详细信息，如名称、符号、令牌ID和图像。\r\n\r\n```jsx\r\nimport React, { useState, useEffect } from 'react';\r\n\r\nconst convertToHttpUrl = (url) => {\r\n  if (typeof url === 'string' && url.startsWith('ipfs://')) {\r\n    return `https://cloudflare-ipfs.com/ipfs/${url.split('ipfs://')[1]}`;\r\n  }\r\n  return url;\r\n};\r\n\r\nconst handleImageError = (e,setImageError) => {\r\n  const ipfsPattern = /ipfs\\/(Qm[1-9A-Za-z]{44}\\/?.*)/;\r\n  const match = e.target.src.match(ipfsPattern);\r\n  if (match) {\r\n    e.target.src = `https://cloudflare-ipfs.com/ipfs/${match[1]}`;\r\n  }\r\n  else {\r\n    setImageError(true);\r\n  }\r\n};\r\n\r\nconst NftCard = ({ nft }) => {\r\n  const maxTokenIdLength = 10;\r\n  const truncatedTokenId = nft.token_id.length > maxTokenIdLength ? nft.token_id.substring(0, maxTokenIdLength) + '...' : nft.token_id;\r\n  const imageUri = nft.image_uri || (nft.metadata ? nft.metadata.image : '');\r\n  const imageUrl = convertToHttpUrl(imageUri);\r\n  const [isVideo, setIsVideo] = useState(false);\r\n  const [imageError, setImageError] = useState(false);\r\n  //console.log('Image URL:', imageUrl); \r\n\r\n  useEffect(() => {\r\n    fetch(imageUrl)\r\n      .then((response) => {\r\n        if (!response.ok) {\r\n          throw new Error('Failed to fetch image');\r\n        }\r\n        const contentType = response.headers.get('Content-Type');\r\n        if (contentType && contentType.startsWith('video')) {\r\n          setIsVideo(true);\r\n        }\r\n      })\r\n      .catch((error) => {\r\n        // This is where errors can be logged or default images can be set, but it will not cause unhandled runtime errors.\r\n        console.error('Error fetching image:', error);\r\n      });\r\n  }, [imageUrl]);\r\n\r\n  return (\r\n    <div style={{ width: '25%', padding: '10px', border: '1px solid #ccc' }}>\r\n      {isVideo ? (\r\n        <video src={imageUrl} controls style={{ width: '100%' }} />\r\n      ): imageError ? (\r\n        <div>NFT not shown</div> // If the image fails to load, display this message.\r\n      ) \r\n       : (\r\n          <img src={imageUrl} alt={nft.name} style={{ width: '100%' }} onError={(e) => handleImageError(e, setImageError)} />\r\n        )}\r\n      <h3>{nft.name}</h3>\r\n      <p><strong>Symbol:</strong> {nft.symbol}</p>\r\n      <p><strong>Token ID:</strong> <span title={nft.token_id}>{truncatedTokenId}</span></p>\r\n      <p><strong>Contract Address:</strong> {nft.contract_address}</p>\r\n    </div>\r\n  );\r\n};\r\n\r\nexport default NftCard;\r\n```\r\n\r\n### **第3步：`SearchBar` 组件**\r\n\r\n- **目的：** 处理用户输入和NFT搜索的MetaMask集成。\r\n- **代码解释**：\r\n    - **状态管理**：管理用户输入的地址和与MetaMask连接的地址。\r\n    - **MetaMask功能**：包括连接和断开功能。\r\n    - **搜索功能**：在用户输入时触发搜索。\r\n\r\n```jsx\r\nimport React, { useState } from 'react';\r\n\r\nconst SearchBar = ({ onSearch }) => {\r\n    const [inputAddress, setInputAddress] = useState(''); // 用户手动输入的地址\r\n    const [connectedAddress, setConnectedAddress] = useState(''); // 从MetaMask连接的地址\r\n    const [contractAddress, setContractAddress] = useState('');\r\n\r\n    const connectMetaMask = async () => {\r\n        if (!window.ethereum) {\r\n            alert('Please install MetaMask first.');\r\n            return;\r\n        }\r\n\r\n        let accounts;\r\n        try {\r\n            accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });\r\n            setConnectedAddress(accounts[0]);\r\n            setInputAddress(''); // 连接后清除地址\r\n        } catch (error) {\r\n            alert('You rejected the request.');\r\n        }\r\n    };\r\n\r\n    const disconnectWallet = () => {\r\n        setConnectedAddress(''); // 清除连接的MetaMask地址\r\n        setInputAddress(''); // 同时清除输入地址\r\n    };\r\n\r\n    const handleSearch = () => {\r\n        const addressToSearch = connectedAddress || inputAddress;\r\n        onSearch(addressToSearch, contractAddress);\r\n    };\r\n\r\n    return (\r\n        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>\r\n            {connectedAddress ? (\r\n                <>\r\n                    <span>Connected Address: {connectedAddress}</span>\r\n                    <button onClick={disconnectWallet}>Disconnect Wallet</button>\r\n                </>\r\n            ) : (\r\n                <>\r\n                    <button onClick={connectMetaMask}>Connect MetaMask</button>\r\n                    <input\r\n                        type=\"text\"\r\n                        placeholder='Your ETH address or ENS name'\r\n                        value={inputAddress}\r\n                        onChange={(e) => setInputAddress(e.target.value)}\r\n                        style={{ margin: '10px 0', width: '300px' }}\r\n                    />\r\n                </>\r\n            )}\r\n            <input \r\n                type=\"text\"\r\n                placeholder='NFT Contract address (optional)'\r\n                value={contractAddress}\r\n                onChange={(e) => setContractAddress(e.target.value)}\r\n                style={{ margin: '10px 0', width: '300px' }}\r\n            />\r\n            <button onClick={handleSearch}>Search</button>\r\n        </div>\r\n    );\r\n};\r\n\r\nexport default SearchBar;\r\n```\r\n\r\n### **第4步：`Home` 组件**\r\n\r\n- **目的：** 作为NFT档案应用程序的主容器。\r\n- **代码解释**：\r\n    - **状态管理**：管理NFT数据和所选的区块链ID的状态。\r\n    - **搜索处理**：根据搜索参数调用API获取NFT数据。\r\n    - **UI组成**：集成应用程序UI的所有子组件。\r\n\r\n```jsx\r\nimport React, { useState } from 'react';\r\nimport axios from 'axios';\r\nimport SearchBar from '../components/SearchBar';\r\nimport NftCard from '../components/NftCard';\r\nimport BlockchainSelector from '../components/BlockchainSelector';  \r\n\r\nconst Home = () => {\r\n  const [nftData, setNftData] = useState(null);\r\n  const [selectedChainId, setSelectedChainId] = useState('');\r\n\r\n  const handleSearch = async (address, contractAddress) => {\r\n    setNftData(null);\r\n    try {\r\n      const response = await axios.get(`/api/chainbaseApi?address=${address}&contract_address=${contractAddress}&chain_id=${selectedChainId}`);\r\n      setNftData(response.data.data);\r\n    } catch (error) {\r\n      console.error(error);\r\n    }\r\n  };\r\n\r\n  return (\r\n    <div style={{ textAlign: 'center' }}>\r\n      <img src=\"/logo.png\" alt=\"Chainbase Logo\" style={{ marginTop: '20px', width: '300px', height: '50px' }} />\r\n      \r\n      <BlockchainSelector setSelectedChainId={setSelectedChainId} />\r\n      <SearchBar onSearch={handleSearch} />\r\n\r\n      {nftData && nftData.length > 0 ? (\r\n        <div>\r\n          <h2>NFTs for Address: {nftData[0].owner}</h2>\r\n          <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>\r\n            {nftData.map((nft) => (\r\n              <NftCard key={`${nft.contract_address}-${nft.token_id}`} nft={nft} />\r\n            ))}\r\n          </div>\r\n        </div>\r\n      ) : (\r\n        <div></div>\r\n      )}\r\n    </div>\r\n  );\r\n};\r\n\r\nexport default Home;\r\n```\r\n\r\n该 NFT 演示中的每个组件都旨在处理特定功能，确保明确的关注点分离。这种模块化方法有助于有效地维护和扩展应用程序。\r\n\r\n### ****第五步：详细解释Chainbase API与ENS API的集成使用****\r\n\r\n在这一步中，我们将深入探讨将Chainbase API集成到我们的NFT演示中的关键方面，特别关注使用ENS API解析以太坊地址的用途。\r\n\r\n- **API处理程序设置**：\r\n    - **目的**：这个服务器端的函数充当中介，处理来自前端的请求，处理它们，并与Chainbase API通信以获取NFT数据。\r\n- **代码解析**：\r\n    - **初始设置和CORS配置**：设置处理程序并配置CORS以允许跨域请求。\r\n\r\n```jsx\r\nexport default async function handler(req, res) {\r\n  // Logging and CORS setup\r\n}\r\n\r\n```\r\n\r\n- **处理ENS名称**：\r\n    - **ENS API调用**：如果提供的地址是ENS名称（以'.eth'结尾），则代码调用Chainbase ENS API将其解析为标准的以太坊地址。\r\n\r\n```jsx\r\nif (address.endsWith('.eth')) {\r\n  // Fetching from Chainbase ENS API\r\n  // Convert ENS to Ethereum address\r\n}\r\n\r\n```\r\n\r\n- **构建Chainbase API请求URL**：\r\n    - 在将ENS名称解析为以太坊地址（或使用直接提供的以太坊地址）之后，该函数构建了用于Chainbase API请求的URL。\r\n    - 它附加了**`chain_id`**，**`address`**和**`contract_address`**等参数到基本URL。\r\n    - \r\n    \r\n    ```jsx\r\n    const baseUrl = `https://api.chainbase.online/v1/account/nfts?chain_id=${chainId}`;\r\n    let targetUrl = `${baseUrl}&address=${address}&limit=100`;\r\n    if (query.contract_address) {\r\n      targetUrl += `&contract_address=${query.contract_address}`;\r\n    }\r\n    \r\n    ```\r\n    \r\n\r\n- **从Chainbase API获取数据**：\r\n    - **API请求**：代码使用**`fetch`**函数通过构建的URL向Chainbase API发出GET请求。\r\n    - **处理响应**：然后解析来自Chainbase API的响应并发送回前端。\r\n\r\n```jsx\r\nconst backendResponse = await fetch(targetUrl, {\r\n  headers: {\r\n    accept: 'application/json',\r\n    'x-api-key': process.env.CHAINBASE_API_KEY,\r\n  },\r\n});\r\n// Processing and sending the response to the front-end\r\n\r\n```\r\n\r\n- **错误处理**：该函数包括错误处理以处理API调用期间可能出现的任何问题，确保前端在失败的情况下也能收到适当的响应。\r\n\r\n```jsx\r\ntry {\r\n  // Chainbase API request and response handling\r\n} catch (error) {\r\n  res.status(500).json({ error: 'Error' });\r\n}\r\n\r\n```\r\n\r\n这个处理程序函数对于NFT演示的功能至关重要，因为它无缝地集成了ENS名称解析和从Chainbase API检索NFT数据。理解这个函数提供了一个全面的视图，说明了如何实现后端逻辑以支持Web应用程序的前端功能。\r\n\r\n### ****5. 测试与部署****\r\n\r\n### Testing Process\r\n\r\n在部署NFT个人资料应用程序之前，在本地开发环境中进行彻底的测试非常重要。\r\n\r\n1. **启动本地开发服务器**：\r\n    - 在终端中运行**`npm run dev`**。此命令启动开发服务器，使您可以在本地访问应用程序。\r\n    - 默认情况下，应用程序应该在**`http://localhost:3000`**或终端中提供的类似URL上可访问。\r\n2. **测试功能**：\r\n    - 在Web浏览器中导航应用程序，确保所有组件都正确呈现。\r\n    - 通过输入不同的地址、ENS名称和合同地址来测试搜索功能和区块链网络选择。确保应用程序正确显示NFT数据或适当处理错误。\r\n3. **检查控制台日志**：\r\n    - 监视控制台，查看可能指示代码问题的任何错误或警告。\r\n    - 验证API调用是否正确执行，并且响应是否按预期处理。\r\n\r\n### **部署指南**\r\n\r\n一旦您的应用程序经过测试并准备就绪，您可以将其部署到诸如Vercel之类的云平台以供公共访问。\r\n\r\n1. **设置部署**：\r\n    - 如果尚未注册，请在[Vercel](https://vercel.com/)上注册帐户。\r\n    - 运行**`npm install -g vercel`**或按照Vercel文档中的说明安装Vercel CLI。\r\n2. **为部署准备您的项目**：\r\n    - 确保项目的**`package.json`**文件正确设置了启动脚本。\r\n    - 在Vercel的项目设置中添加任何环境变量（如您的Chainbase API密钥）这些可以在Vercel仪表板上的项目设置中添加。\r\n3. **部署到Vercel**：\r\n    - 在终端中导航到项目目录。\r\n    - 运行命令**`vercel`**以启动部署。Vercel CLI将指导您完成此过程。\r\n    - 部署后，Vercel将提供一个URL以访问您的部署应用程序。\r\n4. **验证部署**：\r\n    - 访问提供的URL，确保您的NFT个人资料应用程序在实时环境中可以访问并正常运行。\r\n\r\n部署到Vercel等平台提供了易用性、可扩展性和与各种前端框架的集成的优势。记得保持API密钥和敏感数据的安全，并避免在客户端代码中公开它们。\r\n\r\n### **6. Conclusion**\r\n\r\n[在本教程中](https://github.com/daizhengxue/nftdemo)，我们介绍了使用现代Web技术创建NFT个人资料的关键步骤。我们首先通过使用React和Axios等工具设置了我们的环境。然后，我们深入探讨了编码过程，其中我们开发了几个组件，如**`BlockchainSelector`**，**`NftCard`**和**`SearchBar`**，每个在我们的应用程序中扮演着重要的角色。\r\n\r\n我们还探讨了如何集成Chainbase API以获取NFT数据，包括处理以太坊名称服务（ENS）地址。强调了测试和部署的重要性，确保我们的应用程序是健壮且可访问的。\r\n\r\nNFT个人资料的创建不仅突显了区块链技术的实际应用，还展示了NFT在代表数字所有权和身份方面在不断演变的Web空间中的潜力。\r\n\r\n## 拓展阅读和信息\r\n\r\n为了加深对本教程中使用的技术和概念的理解，考虑查阅以下资源：\r\n\r\n1. **IPFS（星际文件系统）**：\r\n    - 了解IPFS的工作原理以及在NFT上下文中处理去中心化文件存储的重要性。\r\n    - [IPFS文档](https://docs.ipfs.io/)\r\n2. **处理NFT中的不同媒体类型**：\r\n    - 了解如何识别和处理NFT元数据中的各种媒体类型，如GIF或视频。\r\n    - [了解NFT元数据](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/)\r\n3. **高级区块链概念**：\r\n    - 更深入地了解区块链技术及其应用，特别是在NFT和数字所有权的背景下。\r\n    - [区块链基础知识](https://www.ibm.com/topics/what-is-blockchain)\r\n4. **React和Next.js文档**：\r\n    - 提升React和Next.js的技能，实现更复杂的前端开发。\r\n    - [React文档](https://reactjs.org/docs/getting-started.html)\r\n    - [Next.js文档](https://nextjs.org/docs)\r\n5. **Vercel部署指南**：\r\n    - 通过使用Vercel高效部署应用程序，获取更多信息。\r\n    - [Vercel部署文档](https://vercel.com/docs)\r\n\r\n通过[本教程](https://github.com/daizhengxue/nftdemo)的学习和探索这些额外的资源，您将为深入了解NFT和区块链技术的世界以及在Web开发和数字资产创建中解锁新的可能性做好充分准备。\r\n\r\n**原文链接：[Create Your Own NFT Gallery  with React and Chainbase API](https://chainbase.com/blog/article/create-your-own-nft-gallery-with-react-and-chainbase-api)**"},"author":{"user":"https://learnblockchain.cn/people/16124","address":"0x7C465D668e6c5E1B5645d59B131b93b0a281d02D"},"history":null,"timestamp":1700824525,"version":1}