React useContext路由跳转异常:从详情页返回首页触发TypeError
问题:从详情页返回首页时
portfolios.map报错 问题场景
直接访问首页或从其他页面跳转首页时,组件能正常渲染所有portfolio;但从通过slug展示的详情页返回首页时,控制台抛出类型错误,页面无内容显示,刷新页面后恢复正常。
首页组件代码
const { portfolios, getPortfolios } = useContext(PortfolioContext); useEffect(() => {getPortfolios()}, []); return (...)
详情页组件代码
const { slug } = useParams(); const { portfolios, getPortfolioFromSlug } = useContext(PortfolioContext); useEffect(() => {getPortfolioFromSlug(slug)}, []); return (...)
报错信息
Uncaught TypeError: portfolios.map is not a function at Portfolio (Portfolio.js:15:1) at renderWithHooks (react-dom.development.js:16305:1) at mountIndeterminateComponent (react-dom.development.js:20074:1) at beginWork (react-dom.development.js:21587:1) at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1) at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1) at invokeGuardedCallback (react-dom.development.js:4277:1) at beginWork$1 (react-dom.development.js:27451:1) at performUnitOfWork (react-dom.development.js:26557:1) at workLoopSync (react-dom.development.js:26466:1) The above error occurred in the <Portfolio> component: at Portfolio (http://localhost:3000/static/js/bundle.js:1315:56) at Routes (http://localhost:3000/static/js/bundle.js:49405:5) at main at Router (http://localhost:3000/static/js/bundle.js:49338:15) at BrowserRouter (http://localhost:3000/static/js/bundle.js:48147:5) at PortfolioState (http://localhost:3000/static/js/bundle.js:1588:86) at App Consider adding an error boundary to your tree to customize error handling behavior. Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
Context中的API函数代码
const getPortfolios = async () => { // API Call const response = await fetch(`http://${host}:${port}/api/portfolios/getportfolios`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const jsonResponse = await response.json(); setPortfolios(jsonResponse); } const getPortfolioFromSlug = async (slug) => { // API Call const response = await fetch(`http://${host}:${port}/api/portfolios/getportfolio/${slug}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const jsonResponse = await response.json(); setPortfolios(jsonResponse); }
问题根源
你在Context里用了同一个portfolios状态存储两种不同类型的数据:
getPortfolios返回的是数组,符合首页map遍历的需求getPortfolioFromSlug返回的是单个对象,调用setPortfolios后会把原本的数组状态覆盖成对象
从详情页返回首页时,组件会先渲染当前Context里的portfolios(此时是对象),然后才会执行useEffect里的getPortfolios获取数组。这时候对对象调用map方法就会抛出类型错误。
修复方案
方案1:拆分状态(推荐,语义更清晰)
在Context里把列表和单个详情拆成两个独立状态,避免类型冲突:
// PortfolioContext内部定义状态 const [portfolioList, setPortfolioList] = useState([]); const [currentPortfolio, setCurrentPortfolio] = useState(null); // 修改API函数 const getPortfolios = async () => { const response = await fetch(`http://${host}:${port}/api/portfolios/getportfolios`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const jsonResponse = await response.json(); setPortfolioList(jsonResponse); } const getPortfolioFromSlug = async (slug) => { const response = await fetch(`http://${host}:${port}/api/portfolios/getportfolio/${slug}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const jsonResponse = await response.json(); setCurrentPortfolio(jsonResponse); }
之后首页用portfolioList.map遍历,详情页直接使用currentPortfolio即可。
方案2:渲染时做类型校验
如果不想改动Context结构,可以在首页组件里先判断portfolios的类型,再执行遍历:
return ( <div> {Array.isArray(portfolios) ? ( portfolios.map(portfolio => ( // 渲染单个portfolio项的逻辑 )) ) : ( // 加载状态或空提示 <div>加载中...</div> )} </div> )
额外优化建议
- 初始化Context状态时,明确
portfolios的初始值为[],避免初始状态类型不确定 - 在API请求的
useEffect里添加清理函数,防止组件卸载后仍更新状态(最佳实践)
内容的提问来源于stack exchange,提问作者Ayush
相关产品推荐
相关产品推荐

