You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

问题原因

  1. 状态更新异步性:dispatch触发状态更新是异步操作,调用addProject后,组件内的projects不会立即更新,此时执行navigate获取的是更新前的旧数组,无法拿到最新项目。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 07:03:17