0

I am creating an app and using useContext+useReducer to manage my state. There is a functionality where I let user create a project and then append this project to projects array state and navigate user to the next screen where he can add more details about the project. I do this using the useNavigate hook from react-router-dom.

When I direct user to the next screen, I am trying to pass the project that he created by sending the last project in the projects array state.

createProject.jsx

import React, { useState, useContext, useReducer } 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";
import projectsReducer from "../context/projects/projects-reducer";

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 [state,dispatch] = useReducer()

  let navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!projectData["project_name"]) {
      alert("Project Name cannot be empty");
      return;
    }
    // Call the Create Project post Service now.
    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); // This method dispatches the add project request and adds the new project which is in the response
          // to the Projects State.
          navigate("/add-project-details", {
            state: { project: projects[projects.length - 1] },
          });
        } else {
        }
      })
      .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>
      {/* <h3 className="darkGrey mb-50">Basic Info</h3> */}
      <form action="POST" 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>
  );
}

Here is the addProject(response) calls a method in projectsState.jsx which in turn dispatches request to add project to the projects array.

projectsState.jsx

import { useEffect, 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;

projectReducer.jsx

import { SET_PROJECTS, ADD_PROJECT, ADD_PRIORITY } from "./projects-actions";
import { getProjectObj, addPriorityToProject } from "./project-util";
import Project from "./project-class";

const projectsReducer = (state, action) => {
  switch (action.type) {
    case SET_PROJECTS:
      const projectObjs = [];

      // Create  project objects and add it in the projects array
      for (const project of action.payload) {
        const projectObj = getProjectObj(project);
        projectObjs.push(projectObj);
      }

      return {
        projects: [...projectObjs],
      };
    case ADD_PROJECT:
      // create Project Object
      const projectObj = getProjectObj(action.payload);
      console.log(projectObj);
      return {
        projects: [...state.projects, projectObj],
      };
    case ADD_PRIORITY:
      // These lines of code are being called after the navigate statement in the createProject file (line 41).
      // Because of which the user is being redirected to the next screen without the new project added. 
      let projects = addPriorityToProject(action.payload, state.projects);
      console.log(projects);
      return {
        ...state,
        project: [...projects],
      };
    default:
      return state;
  }
};

export default projectsReducer;

But when I run the code after creating the project when user is getting redirected to additionalDetails page, the project that is being passed is not the latest one.

When I use debugger I found that the addProject gets called and then it calls the dispatcher but after that the control comes back to the createProject file and the useNavigate line is executed and then the control goes back and updates the project in the projects array. But by that time the user is already navigated to the next screen with incorrect project.

Can you please help me understand what I am doing wrong.

Thanks!

ms394
  • 109
  • 7

1 Answers1

0

One way is set up a useEffect and an effect dependency with projects.length:

const { addProject, projects } = useContext(ProjectContext);
const navigate = useNavigate();

useEffect(() => {
  navigate("/add-project-details", {
    state: { project: projects[projects.length - 1] },
  });
},[navigate, projects])

Only when the actual project gets pushed, should we trigger the navigate function, and then you will get the last data in the array and a correct page.

Second way may not work, but worth trying out:

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!projectData["project_name"]) {
      alert("Project Name cannot be empty");
      return;
    }
    // Call the Create Project post Service now.
    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); 
        } else {
        }
      })
      .then(() => { // seperate addProject and navigate function
        navigate("/add-project-details", {
          state: { project: projects[projects.length - 1] },
        });
      })
      .catch((err) => {
        console.log(err);
      });
    setProjectData({
      // ...projectData, // <- no need for this to reset to default state
      project_name: "",
      project_description: "",
    });
  };
Enfield Li
  • 1,952
  • 1
  • 8
  • 23
  • Hi @Enfield li thanks for your comment, I tried the first method that you mentioned but now when I navigate to Create Projects page, the user is directly getting redirecting to additional-details-project page. – ms394 Jul 12 '22 at 05:07
  • Try adding `navigate` as a dependency as well. Answer updated. – Enfield Li Jul 12 '22 at 07:47