React组件挂载时不渲染数据,重渲染后正常的问题求助
问题:组件挂载时无数据显示,重渲染后才正常展示
我在完成GreatFrontEnd的任务,需要从Job API获取数据:先获取任务ID列表,再遍历ID获取对应的Job对象数组。现在遇到问题:组件挂载时没有数据显示,但修改代码触发重渲染(比如增删括号)后数据能正常展示。之前把两个请求放在独立的useEffect里能正常运行,现在想精简代码就出问题了,代码如下:
import React, { useState, useEffect } from 'react'; export default function App() { const [jobsIds, setJobIds] = useState(null); const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { getJobs(); console.log(jobs) }, []); const getJobIds = async () => { try { const idRes = await fetch(`https://hacker-news.firebaseio.com/v0/jobstories.json`); const jobIds = await idRes.json(); setJobIds(jobIds); } catch (error) { console.error('Error fetching job IDs:', error); } } const getJobs = async () => { await getJobIds() try { setLoading(true); const jobsData = await Promise.all( jobsIds.map(async (id) => { const jobRes = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`); return await jobRes.json(); }) ); setJobs(jobsData) setLoading(false); } catch (error) { console.error('Error fetching jobs:', error); setLoading(false); } }; return ( <> {loading? <p> Loading.... </p>: jobs.map((job) =>( <p> {job.title} </p> ))} </> ); }
问题根源
核心问题是React状态更新是异步的:你在getJobs里用await getJobIds()等待ID请求完成,但setJobIds(jobIds)只是触发状态更新,并不会立刻改变jobsIds的值。所以当你紧接着调用jobsIds.map时,jobsIds还是初始的null,这会导致Cannot read property 'map' of null的错误——虽然被try-catch捕获,但后续获取数据的逻辑直接中断,最终jobs还是空数组,所以组件挂载时没数据。
而当你修改代码触发重渲染时,jobsIds已经在第一次请求后被更新为有效数组,此时再次执行getJobs就能正常调用map获取数据了。
修复方案
方案1:直接返回ID数组,跳过状态存储
既然getJobIds的唯一作用是给后续请求提供ID,没必要把ID存在状态里,直接让函数返回ID数组即可,彻底避免状态异步更新的问题:
修改getJobIds:
const getJobIds = async () => { try { const idRes = await fetch(`https://hacker-news.firebaseio.com/v0/jobstories.json`); return await idRes.json(); // 直接返回ID数组 } catch (error) { console.error('Error fetching job IDs:', error); return []; // 出错时返回空数组,避免后续map报错 } }
修改getJobs:
const getJobs = async () => { try { setLoading(true); const jobIds = await getJobIds(); // 直接拿到返回的ID数组 const jobsData = await Promise.all( jobIds.map(async (id) => { const jobRes = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`); return await jobRes.json(); }) ); setJobs(jobsData); setLoading(false); } catch (error) { console.error('Error fetching jobs:', error); setLoading(false); } };
方案2:监听jobsIds状态变化(保留ID状态的场景)
如果后续需要用到jobsIds状态,可以把获取详情的逻辑放到监听jobsIds的useEffect里,确保ID更新后再请求数据:
useEffect(() => { const fetchJobs = async () => { if (!jobsIds) return; // ID为空时不执行 try { setLoading(true); const jobsData = await Promise.all( jobsIds.map(async (id) => { const jobRes = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`); return await jobRes.json(); }) ); setJobs(jobsData); setLoading(false); } catch (error) { console.error('Error fetching jobs:', error); setLoading(false); } }; fetchJobs(); }, [jobsIds]); // 监听jobsIds变化,有值时执行请求 useEffect(() => { getJobIds(); // 挂载时先获取ID }, []);
完整修复代码(方案1)
import React, { useState, useEffect } from 'react'; export default function App() { const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { getJobs(); }, []); const getJobIds = async () => { try { const idRes = await fetch(`https://hacker-news.firebaseio.com/v0/jobstories.json`); return await idRes.json(); } catch (error) { console.error('Error fetching job IDs:', error); return []; } } const getJobs = async () => { try { setLoading(true); const jobIds = await getJobIds(); const jobsData = await Promise.all( jobIds.map(async (id) => { const jobRes = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`); return await jobRes.json(); }) ); setJobs(jobsData); setLoading(false); } catch (error) { console.error('Error fetching jobs:', error); setLoading(false); } }; return ( <> {loading ? <p> Loading.... </p> : jobs.map((job) => ( <p key={job.id}> {job.title} </p> {/* 添加key避免React列表渲染警告 */} ))} </> ); }
内容的提问来源于stack exchange,提问作者Aidan McCormick-Buczko
相关产品推荐
相关产品推荐

