修改UseState Hook后React组件未重新渲染问题求助
React状态更新后组件不渲染的问题排查与修复
我查了一堆相关资料都没解决这个问题——用useEffect Hook获取nftRewards对象数组,控制台能正常打印数据,但依赖这个状态的渲染部分在状态更新后完全没反应,根本不渲染。相关代码和未渲染的渲染片段如下:
原逻辑代码:
const [campaignRewardsJson, setCampaignRewardsJson] = useState<nftReward[]>([]); const { data: rewards } = useContractRead({ address: campaignsId, abi: campaignABI, functionName: 'getRewards', }); async function getRewardObject() { var array: nftReward[] = []; rewards.map(async (reward, index) => { const json = fetch(reward[2]); const data = await json.then((res) => res.json()); const nft: nftReward = { title: data.name, description: data.description, imageURL: data.image, price: reward[0], quantity: reward[1], }; array.push(nft); }); return array; } type nftReward = { title: string; description: string; imageURL: string; price: number; quantity: number; }; useEffect(() => { if (rewards) { const fetchData = async () => { var data = await getRewardObject(); setCampaignRewardsJson(data); }; fetchData().catch(console.error); } }, [rewards]);
未被渲染的代码片段:
{campaignRewardsJson.map((reward, index) => ( <Card direction={{ base: 'column', sm: 'row' }} overflow="hidden" variant="outline"> <Image objectFit="cover" maxW={{ base: '100%', sm: '200px' }} src={reward.imageURL} alt="Caffe Latte" /> <Stack> <CardBody> <Heading size="md">{reward.title}</Heading> <Text py="2">{reward.description}</Text> </CardBody> <CardFooter> <Button variant="solid" colorScheme="blue"> Mint NFT </Button> </CardFooter> </Stack> </Card> ))}
问题根源
核心问题在getRewardObject的异步处理逻辑:
rewards.map里用了async回调,map会直接返回一组Promise,但不会等待这些异步操作完成- 你在循环结束后直接返回
array,此时所有fetch请求还在pending状态,array是空的,所以setCampaignRewardsJson设置的是空数组,渲染部分自然没有内容
修复方案
用Promise.all等待所有异步请求完成后再收集结果,修改后的核心代码如下:
修正后的getRewardObject函数
async function getRewardObject() { if (!rewards) return []; // 将每个异步请求包装成Promise,存入数组 const promiseList = rewards.map(async (reward) => { const res = await fetch(reward[2]); const data = await res.json(); return { title: data.name, description: data.description, imageURL: data.image, price: reward[0], quantity: reward[1], } as nftReward; }); // 等待所有Promise完成,返回填充好的数组 return await Promise.all(promiseList); }
完整优化后的代码
// 类型定义移到组件外部,避免重复创建 type nftReward = { title: string; description: string; imageURL: string; price: number; quantity: number; }; function YourComponent() { const [campaignRewardsJson, setCampaignRewardsJson] = useState<nftReward[]>([]); const { data: rewards } = useContractRead({ address: campaignsId, abi: campaignABI, functionName: 'getRewards', }); async function getRewardObject() { if (!rewards) return []; const promiseList = rewards.map(async (reward) => { const res = await fetch(reward[2]); const data = await res.json(); return { title: data.name, description: data.description, imageURL: data.image, price: reward[0], quantity: reward[1], }; }); return await Promise.all(promiseList); } useEffect(() => { const fetchData = async () => { const data = await getRewardObject(); setCampaignRewardsJson(data); }; if (rewards) { fetchData().catch(console.error); } }, [rewards]); return ( <div> {campaignRewardsJson.map((reward, index) => ( {/* 必须添加key属性,符合React列表渲染规范 */} <Card key={index} direction={{ base: 'column', sm: 'row' }} overflow="hidden" variant="outline"> <Image objectFit="cover" maxW={{ base: '100%', sm: '200px' }} src={reward.imageURL} alt={reward.title} /> <Stack> <CardBody> <Heading size="md">{reward.title}</Heading> <Text py="2">{reward.description}</Text> </CardBody> <CardFooter> <Button variant="solid" colorScheme="blue"> Mint NFT </Button> </CardFooter> </Stack> </Card> ))} </div> ); }
额外优化点
- 给列表项添加
key属性,这是React列表渲染的强制规范,避免不必要的性能问题 - 将
nftReward类型定义移到组件外部,避免组件每次渲染时重新创建类型 - 给
rewards增加空值判断,防止初始状态为undefined时触发报错
内容的提问来源于stack exchange,提问作者Jeks
相关产品推荐
相关产品推荐

