如何用React Context保存嵌套数组?读取Keywords遇TypeError报错
问题
通过fetch请求获取包含Documents数组的JSON数据,每个Document对象嵌套Keywords数组。目前能将Documents数组存入React Context,但保存Keywords数组时触发错误:Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'Keywords')。已成功将第一个Document的Keywords存入localStorage,但无法存入Context,最终需求是保存所有Document的Keywords数组,用于生成以Keywords的Name为列名、Value为表格数据的表格。
错误分析
- dispatch调用错误:代码中尝试一次传递两个action对象给dispatch,但React的useReducer dispatch仅支持单个action,第二个action会被忽略,导致
SET_KEYWORDS未被正确触发。 - 状态覆盖问题:reducer处理
SET_WORKVIEW和SET_KEYWORDS时,直接返回新对象而未保留原有状态的其他属性,会导致其中一个状态被覆盖(比如设置workview后,keywords会变为undefined,反之亦然)。 - 空值未校验:直接访问
json.Documents[0].Keywords未做空值校验,若Documents为空数组,会触发Cannot read properties of undefined错误。 - 冗余序列化:存入Context时对Keywords执行
JSON.stringify,导致Context中存储的是字符串而非数组,后续map遍历会出错。
修复方案
核心修改点
- 拆分dispatch调用,分别更新workview和keywords状态
- reducer中使用扩展运算符
...state保留原有状态属性 - 添加空值校验,避免访问空数组元素触发错误
- 移除冗余的JSON序列化,直接传递数组到Context
- 通过
flatMap合并所有Document的Keywords数组(满足“保存所有Keywords”的需求)
修改后的代码
Dashboard.jsx
import { useEffect } from "react"; import { useWorkViewContext } from "../hooks/useWorkViewContext"; function Dashboard({ user }) { const { workview, keywords, dispatch } = useWorkViewContext(); useEffect(() => { const fetchWorkView = async () => { const credentials = import.meta.env.VITE_SERVICE_USER + ":" + import.meta.env.VITE_SERVICE_PASSWORD; const response = await fetch( "https://urlforfetch.com", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Basic ${credentials}`, }, body: JSON.stringify({ WorkViewApplication: "VAPR", WorkViewClassName: "Policy", WorkViewFilter: "All Policy Record", SearchAttributes: [ { Name: "InsuranceCarrier", Value: "MAPFRE", }, ], }), } ); const json = await response.json(); if (response.ok && json.Documents?.length > 0) { // 存入localStorage localStorage.setItem("workview", JSON.stringify(json.Documents)); // 收集所有Document的Keywords数组 const allKeywords = json.Documents.flatMap(doc => doc.Keywords || []); localStorage.setItem("keywords", JSON.stringify(allKeywords)); // 分别调用dispatch更新状态 dispatch({ type: "SET_WORKVIEW", payload: json.Documents }); dispatch({ type: "SET_KEYWORDS", payload: allKeywords }); } }; if (user) { fetchWorkView(); } }, [dispatch, user]); return ( <main> <div className="dashboard"> <h1> Welcome back, <span className="accent">{user.username}</span> </h1> <div className="grid-sm"> <div className="grid-sm"> <p>In this section you will find you cases information.</p> </div> <div className="grid-lg"> <h2>My Cases</h2> {workview && workview.length > 0 && ( <div className="grid-md"> {workview.map((document, index) => ( <a key={index} href={document.DocumentURL}> {document.DocumentName} </a> ))} </div> )} {keywords && keywords.length > 0 && ( <div className="grid-md"> {keywords.map((keyword, index) => ( <a key={index}>{keyword.Name}</a> ))} </div> )} </div> </div> </div> </main> ); } export default Dashboard;
WorkViewContext.jsx
import { createContext, useReducer } from "react"; export const WorkViewContext = createContext(); export const workviewReducer = (state, action) => { switch (action.type) { case "SET_WORKVIEW": return { ...state, // 保留原有状态 workview: action.payload, }; case "SET_KEYWORDS": return { ...state, // 保留原有状态 keywords: action.payload, }; default: return state; } }; export const WorkViewContextProvider = ({ children }) => { const [state, dispatch] = useReducer(workviewReducer, { workview: null, keywords: null, }); return ( <WorkViewContext.Provider value={{ ...state, dispatch }}> {children} </WorkViewContext.Provider> ); };
内容的提问来源于stack exchange,提问作者Rafael
相关产品推荐
相关产品推荐

