React Query加载数据时useReducer初始值为undefined如何解决?
问题根因
核心原因有2个:
useReducer的第二个参数是初始化唯一值,只会在Hook第一次挂载时读取一次,后续useQuery加载完成后userData从undefined变为有效值时,useReducer不会自动同步更新内部状态,因此初始状态会一直停留在undefined。- 你之前尝试把加载判断放在
useUserDataForm调用前会报错,是因为会导致Hook调用顺序不稳定,违反React Hook的调用规则。
解决方案
方案1:给useReducer加重置逻辑(改造成本最低)
在reducer中新增重置action,监听userData的变化,加载完成后主动将最新的userData同步到reducer状态中,同时增加兜底值避免初始状态为undefined。
修改reducer代码:
function userDataFormReducer(state, action) { switch(action.type) { // 新增重置action,直接用传入的用户数据覆盖状态 case "reset": return action.payload; case "currency": return {...state, currency: action.currency} default: // 原代码此处返回undefined属于bug,匹配不到action时必须返回原状态 return state; } }
修改useUserDataForm代码:
function useUserDataForm(handleSubmit, userData) { // 初始状态兜底为空对象,避免undefined引发的报错 const [state, dispatch] = React.useReducer(userDataFormReducer, userData || {}); // 监听userData变化,加载完成后同步重置表单状态 React.useEffect(() => { if (userData) { dispatch({ type: 'reset', payload: userData }); } }, [userData]); // isDirty逻辑增加判断,避免数据未加载时误判 const isDirty = userData ? JSON.stringify(userData) !== JSON.stringify(state) : false; const updateFunction = (type, value) => { dispatch({type, value}); } // 补全原代码遗漏的return逻辑 return { isDirty, methods: { submit: handleSubmit, updateFunction } }; }
主表单代码无需调整,原加载判断逻辑保留即可。
方案2:组件拆分(更推荐,逻辑更干净)
将表单核心逻辑抽离为子组件,父组件只做加载判断,加载完成后再渲染子组件,从根源上避免useReducer初始化时userData为undefined的问题。
调整后的主表单代码:
// 抽离的表单核心子组件,仅在数据加载完成后渲染 function UserFormContent({ userData, handleSubmit }) { const { isDirty, methods } = useUserDataForm(handleSubmit, userData); const { submit, updateFunction } = methods; const rows = React.useMemo(() => { if (userData) { /* Process userData here into arrays */ } return []; }, [userData]); return ( <> 原表单渲染逻辑 *isDirty* 用于检测修改、控制更新按钮启用状态 </> ) } // 父组件仅做数据拉取和加载判断 function UserAccountDataForm({userId}) { const { query: {data: userData, isLoading: isLoadingUserData} } = useUserData(userId); if (isLoadingUserData) { return <AccordionSkeleton /> } // 加载完成后才渲染子组件,此时userData一定有有效值 return <UserFormContent userData={userData} handleSubmit={handleSubmit} /> }
该方案不需要修改原有reducer和useUserDataForm的基础逻辑,也不会产生额外的重置副作用,Tab切换时组件重新挂载也不会出现状态遗留问题。
额外注意事项
- 原
useUserData封装中return { query }写在了函数外部,属于语法错误,需要移到函数内部。 - 避免直接用
JSON.stringify判断对象相等,若userData包含函数、Symbol、循环引用等特殊结构会报错,可按需引入深比较工具方法代替。
内容的提问来源于stack exchange,提问作者zack_falcon
相关产品推荐
相关产品推荐

