Home > database >  React bug in my app. Trying to spread nested objects to update the data but I believe I am calling i
React bug in my app. Trying to spread nested objects to update the data but I believe I am calling i

Time:08-11

I am new to React and have been stuck on this bug dealing with nested objects for a CV Creator app.

I am successfully passing up the new user data to the component I want to display it on but upon making the setState call I am duplicating the nested objects somehow. I have tried researching nested objects to find the root cause of this bug but have had no luck.

Here is the captured data being passed up to the component in this form.

    const enteredData = [
      {
        id: Math.random(),
        projectName: projectName,
        narrative: [
          {
            id: Math.random(),
            text: narrative,
          },
        ],
      },
    ];

Here is the dummyData I am trying to update looks like this:

const dummyData = [
  {
    id: 1,
    projectName: "Labor Plan",
    narrative: [
      {
        id: 1,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
      {
        id: 2,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
    ],
  },
  {
    id: 2,
    projectName: "React Project",
    narrative: [
      {
        id: 1,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
      {
        id: 2,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
    ],
  },
];

Here is the useState call that is being duplicated.

const Projects = () => {
  const [data, setData] = useState(dummyData);
  const [hovered, setHovered] = useState(false);
  const toggleHover = () => setHovered(!hovered);

  const saveDataHandler = (newData) => {
    setData((prevData) => {
      let idx = prevData.map((item, index) => {
        if (item.projectName === newData[0].projectName) {
          return index;
        }
      });
      console.log(idx);
      return [...newData, ...prevData];
    });
  };

So the above code is 1 of about 20 iterations I have tried thus far. The goal with the code is if the project names are equal I want to spread the new narrative data to the current list (this is the issue I am having a problem with --- currently not returning that in this iteration). If the project name does not exist then I can continue with the current return statement i have which is return [...newData, ...prevData];

enter image description here Here is an image of the console double print.

Desired Output enter image description here

So the desired output is when someone submits a project name that already exists it append the entered data to the existing data. This means keep the original Project information and just update the narrative key by spreading the new data on top of the existing data. If the project name does not exist I want just normally spread the list.

EXTRA FULL COMPONENT CODE IF NEEDED

Bug in This code:

import React, { useState } from "react";
import ProjectsEdit from "./ProjectsEdit";

const dummyData = [
  {
    id: 1,
    projectName: "Labor Plan",
    narrative: [
      {
        id: 1,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
      {
        id: 2,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
    ],
  },
  {
    id: 2,
    projectName: "React Project",
    narrative: [
      {
        id: 1,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
      {
        id: 2,
        text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, possimus. Ex sunt harum, impedit sint facilis ipsam eligendi nam nobis unde officia recusandae eum placeat ab! Quo, tempora amet.",
      },
    ],
  },
];

const Projects = () => {
  const [data, setData] = useState(dummyData);
  const [hovered, setHovered] = useState(false);
  const toggleHover = () => setHovered(!hovered);

  const saveDataHandler = (newData) => {
    setData((prevData) => {
      let idx = prevData.map((item, index) => {
        if (item.projectName === newData[0].projectName) {
          return index;
        }
      });
      console.log(idx);
      return [...newData, ...prevData];
    });
  };
  return (
    <div
      className={
        !hovered
          ? "contact-container main-content-subcontent editInactive"
          : "contact-container main-content-subcontent editActive"
      }
    >
      <h3 className="main-section-header" onm ouseEnter={toggleHover}>
        Projects
      </h3>
      <div className="project-container">
        {data.map((item) => {
          return (
            <React.Fragment key={item.id}>
              <div className="sub-project-container">
                <h4 className="main-section-subHeader">{item.projectName}</h4>
                <ul className="project-list" key={item.id}>
                  {item.narrative.map((point) => {
                    return <li key={point.id}>{point.text}</li>;
                  })}
                </ul>
              </div>
            </React.Fragment>
          );
        })}
      </div>
      <ProjectsEdit saveData={saveDataHandler} toggleHover={toggleHover} />
    </div>
  );
};

export default Projects;

The child component passing up the data

import { useState } from "react";

const ProjectsEdit = (props) => {
  const [projectName, setProjectName] = useState("");
  const [narrative, setNarrative] = useState("");

  const projectHandler = (e) => {
    setProjectName(e.target.value);
  };

  const narrativeHandler = (e) => {
    setNarrative(e.target.value);
  };

  const submitHandler = (e) => {
    e.preventDefault();

    const enteredData = [
      {
        id: Math.random(),
        projectName: projectName,
        narrative: [
          {
            id: Math.random(),
            text: narrative,
          },
        ],
      },
    ];

    props.saveData(enteredData);
    setNarrative("");
    setProjectName("");
  };

  return (
    <div className="edit-main">
      <form
        action=""
        onSubmit={submitHandler}
        className="form-container main-content-form"
      >
        <label htmlFor="" className="form-label">
          Project Name
        </label>
        <input
          type="text"
          onChange={projectHandler}
          className="form-input"
          value={projectName}
        />
        <label htmlFor="" className="form-label">
          Narrative
        </label>
        <textarea
          name=""
          id=""
          cols="30"
          rows="5"
          className="form-input"
          onChange={narrativeHandler}
          value={narrative}
        ></textarea>
        <button
          type="button"
          className="form-cancel form-button"
          onClick={props.toggleHover}
        >
          Close
        </button>
        <button
          type="submit"
          className="form-submit form-button"
          onClick={props.toggleHover}
        >
          Submit
        </button>
      </form>
    </div>
  );
};

export default ProjectsEdit;

CodePudding user response:

Not map(), use findIndex instead

You should not use map(), you can try this code:

  const saveDataHandler = (newData) => {
    const target = [...data];
    const existedProjectIdx = target.findIndex(item => item.projectName === newData.projectName);

    // if not find, 'existedProjectIndex' will be -1 
    if (existedProjectIdx !== -1) {
       const { narrative } = target[existedProjectIdx];
       target[existedProjectIdx].narrative = [...narrative, ...newData.narrative]
    } else {
       target.push(newData)
    }
    
    setData(target)
  };

CodePudding user response:

If I understand correctly, you want to merge the narrative arrays (via concatenation) for any same projectName entries and prepend any new entries.

The idea would be to find an entry by projectName. If you find it, alter the array at that position. Otherwise, prepend the new entry.

I also see absolutely no reason to wrap enteredData in an array.

setData((prev) => {
  const foundIndex = prev.findIndex(
    ({ projectName }) => projectName === newData.projectName
  );
  if (foundIndex === -1) {
    return [newData, ...prev]; // not found, just prepend
  }

  return [
    ...prev.slice(0, foundIndex),
    {
      ...prev[foundIndex],
      narrative: [...prev[foundIndex].narrative, ...newData.narrative],
    },
    ...prev.slice(foundIndex   1),
  ];
});

As per React guidelines...

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

This is why it's best to compute the new value within the state setter function.

  • Related