useReducer状态更新异步导致导航传递最新项目失败求助
问题分析与解决方案
问题场景
使用useContext+useReducer管理状态,用户创建项目后将项目追加到projects数组,再通过useNavigate跳转并传递最新项目,但跳转时传递的是旧数据——因为dispatch触发状态更新后,代码立即执行跳转,此时projects数组尚未完成更新。同时存在useReducer的switch case逻辑错误。
代码片段
createProject.jsx
import React, { useState, useContext } from "react"; import FormField from "../components/formField"; import { useNavigate } from "react-router-dom"; import TextAreaField from "../components/TextFied"; import Button from "../components/button"; import ProjectContext from "../context/projects/projects-context"; export default function CreateProjectPage() { const [projectData, setProjectData] = useState({ project_name: "", project_description: "", }); const handleChange = (e) => { const { name, value } = e.target; setProjectData({ ...projectData, [name]: value }); }; const { addProject, projects } = useContext(ProjectContext); const navigate = useNavigate(); const handleSubmit = (e) => { e.preventDefault(); if (!projectData["project_name"]) { alert("Project Name cannot be empty"); return; } fetch("http://localhost:5000/project/createProject", { credentials: "include", method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(projectData), }) .then(async (res) => { const response = await res.json(); if (res.status == 201) { addProject(response); navigate("/add-project-details", { state: { project: projects[projects.length - 1] }, }); } }) .catch((err) => { console.log(err); }); setProjectData({ ...projectData, project_name: "", project_description: "", }); }; return ( <div> <h2>Create Project</h2> <div className="timeline"> <span className="active">1</span> <span className="inactive">2</span> </div> <form onSubmit={handleSubmit}> <div className="form-field-block"> <FormField name="project_name" type="text" value={projectData["project_name"]} onChange={handleChange} label="Project Name" /> </div> <div className="form-field-block"> <TextAreaField name="project_description" type="text" label="Project Description" onChange={handleChange} value={projectData["project_description"]} /> </div> <Button style="primary" type="submit" value="Create Project" /> </form> </div> ); }
projectsState.jsx
import { useReducer } from "react"; import { SET_PROJECTS, ADD_PROJECT, ADD_PRIORITY } from "./projects-actions"; import projectsReducer from "./projects-reducer"; import ProjectsContext from "./projects-context"; const ProjectsState = (props) => { const initialState = { projects: [], }; const [state, dispatch] = useReducer(projectsReducer, initialState); const setProjects = (projects) => { dispatch({ type: SET_PROJECTS, payload: projects, }); }; const addProject = (project) => { dispatch({ type: ADD_PROJECT, payload: project, }); }; const addPriority = (priorityObj) => { dispatch({ type: ADD_PRIORITY, payload: priorityObj, }); }; return ( <ProjectsContext.Provider value={{ projects: state.projects, addProject, setProjects, addPriority }} > {props.children} </ProjectsContext.Provider> ); }; export default ProjectsState;
projectsReducer.jsx
import { SET_PROJECTS, ADD_PROJECT, ADD_PRIORITY } from "./projects-actions"; import { getProjectObj, addPriorityToProject } from "./project-util"; const projectsReducer = (state, action) => { switch (action.type) { case SET_PROJECTS: const projectObjs = []; for (const project of action.payload) { const projectObj = getProjectObj(project); projectObjs.push(projectObj); } return { projects: [...projectObjs], }; case ADD_PROJECT: const projectObj = getProjectObj(action.payload); console.log(projectObj); return { projects: [...state.projects, projectObj], }; case ADD_PRIORITY: let projects = addPriorityToProject(action.payload, state.projects); console.log(projects); return { ...state, project: [...projects], }; default: return state; } }; export default projectsReducer;
问题原因
- 状态更新异步性:
dispatch触发状态更新是异步操作,调用addProject后,组件内的projects不会立即更新,此时执行navigate获取的是更新前的旧数组,无法拿到最新项目。 - Reducer键名错误:
ADD_PRIORITY分支中,返回的状态键名是project而非projects,会导致状态结构异常,后续读取项目列表时出现错误。
解决方案
1. 修复Reducer的键名错误
修改projectsReducer.jsx中ADD_PRIORITY分支的返回值,将project改为projects:
case ADD_PRIORITY: let projects = addPriorityToProject(action.payload, state.projects); console.log(projects); return { ...state, projects: [...projects], // 修正键名与初始状态一致 };
2. 解决跳转传递旧数据的问题
提供两种可选方案:
方案一:直接使用接口返回的处理后数据(推荐)
既然addProject内部会将接口返回的response转换为projectObj,我们可以提前执行该转换,直接用转换后的对象作为跳转参数,无需等待状态更新:
// 在createProject.jsx中导入转换工具函数 import { getProjectObj } from "../context/projects/project-util"; // 修改handleSubmit中的跳转逻辑 if (res.status == 201) { const newProject = getProjectObj(response); addProject(response); // 保持与reducer逻辑一致 navigate("/add-project-details", { state: { project: newProject }, }); }
方案二:监听状态更新后再跳转
通过useEffect监听projects数组的变化,当数组更新完成后再执行跳转:
// 在createProject.jsx中添加状态和监听逻辑 import React, { useState, useContext, useEffect } from "react"; export default function CreateProjectPage() { const [projectData, setProjectData] = useState({ project_name: "", project_description: "", }); const [shouldNavigate, setShouldNavigate] = useState(false); // 添加跳转标记 // ... 其他原有代码 const handleSubmit = (e) => { e.preventDefault(); if (!projectData["project_name"]) { alert("Project Name cannot be empty"); return; } fetch("http://localhost:5000/project/createProject", { credentials: "include", method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(projectData), }) .then(async (res) => { const response = await res.json(); if (res.status == 201) { addProject(response); setShouldNavigate(true); // 设置跳转标记 } }) .catch((err) => { console.log(err); }); setProjectData({ ...projectData, project_name: "", project_description: "", }); }; // 监听状态变化执行跳转 useEffect(() => { if (shouldNavigate && projects.length > 0) { navigate("/add-project-details", { state: { project: projects[projects.length - 1] }, }); setShouldNavigate(false); // 重置标记,避免重复跳转 } }, [shouldNavigate, projects, navigate]); // ... 原有渲染代码 }
内容的提问来源于stack exchange,提问作者ms394
相关产品推荐
相关产品推荐

