Ronald Langeveld
Ronald Langeveld

Reputation: 744

React JS, Updating a specific field, rendered from a map

I've got a loop (using Array.map) that outputs a bunch of forms with unique id's / keys. I now need to be able to edit/submit them uniquely.

All fields should be empty by default.

At the moment, I haven't figured out how to differentiate them onChange causing the state to be the same on ALL fields.

I need each field to update separately with its unique data.

Basically, I've got a bunch of "Sections" and inside of each "Section", I want to write post tasks.

I'm running the latest React JS and I'm using Hooks.

I've structured this part of the app as follows:

Parent (gets the data of the sections from DB and puts it into a state)

Section List (uses props from Parents and Maps the inputs and some design stuff)

Input Forms, rendered by the map. (These are the forms I want unique.)

Here's code that I've tried. Including, indexing and getting the unique _id's etc.

Inside the Parent

    <ProjectSections Sections={getProjectSections}
    SetSections={setProjectSections} />

Section List

function ProjectSections(props) {

  const setSectionData = props.SetSections;
  const SectionData = props.Sections;

  const blankTask = {taskname: ""}
  const [task, setTask] = useState({taskname: ""})
  const [taskidx, setTaskidx] = useState({idx: ""})
  console.log(task, taskidx)


const taskInputChange = (e) => {
  // onChange={e => [props.SetTask({taskname: e.currentTarget.value})][props.SetIDX(e.currentTarget.dataset.idx)]}

  e.preventDefault();
  setTask({taskname: e.target.value})
  console.log(e.target.dataset.idx)
  // const updatedTasks = [e.currentTarget.dataset.idx][e.target.className] = e.target.value;
  // setTask(updatedTasks)



 // updatedTasks[e.target.dataset.idx][e.target.c] = e.target.value;
  // console.log(updatedTasks)
}

const addTask = (e) => {

    e.preventDefault();

// This is where I will post to the database



  };


  const TheSectionList = SectionData.map((project, idx) =>

    <div key={project._id}>


          <p className="is-size-5">{project.sectionname}</p>

        <AddTaskForm
            IDX={idx}
            Submit={addTask}
            SetTask={setTask}
            theTask={task}
            SetIDX={setTaskidx}
            inputChange={taskInputChange}
             /> 

        </div>



  )

  return (
    <>
    {TheSectionList}
    </>
  )
}


export default ProjectSections;

The Form (AddTaskForm)

function AddTaskForm(props) {


    return (
        <>
            <form onSubmit={props.Submit} >

    <input 
    onChange={props.inputChange}
    type="text" 
    placeholder="Create Homepage" 
    value={props.theTask.taskname}
    data-idx={props.IDX}
     />
  </div>

    <input type="submit" value="Add Task"  />

  </div>
</div>          
            </form>
        </>
    );
};

export default AddTaskForm;

As it is right now, whenever I update (write into) any form, they ALL change, despite the idx showing correctly in the console log.

Upvotes: 2

Views: 2237

Answers (1)

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15688

The scope of your application can be alot larger than what you've suggested in your original post.

In any case, I've created a moderately sized prototype for you. This should demonstrate how to encapsulate each Section, so that their task-list and functionality are isolated to their own context.

See here for: Working Demo

In total there are only 3 child-components and 1 Parent.

index.js

The main sections-state is managed here. All state-updating functions are derived from here.

import React, { useState } from "react";
import ReactDOM from "react-dom";
import AddSectionForm from "./AddSectionForm";
import ProjectSections from "./ProjectSections";

import "./styles.css";

function App() {
  const [sections, setSections] = useState([
    {
      id: 1,
      sectionName: "Chores",
      tasks: [{ id: 1, desc: "Take out trash", editting: false }]
    }
  ]);

  const addNewSection = sectionName => {
    let newSection = {
      id: keyRandomizer(),
      sectionName: sectionName,
      tasks: []
    };

    setSections([...sections, newSection]);
  };

  const addNewTask = (sectionId, taskName) => {
    let newTask = {
      id: keyRandomizer(),
      desc: taskName,
      editting: false
    };

    let allSectionsCopy = JSON.parse(JSON.stringify(sections));
    let sectionIndex = allSectionsCopy.findIndex(
      section => section.id === sectionId
    );
    let sectionToUpdate = allSectionsCopy[sectionIndex];

    sectionToUpdate.tasks = [...sectionToUpdate.tasks, newTask];

    setSections([...allSectionsCopy]);
  };

  const updateTaskDesc = (desc, sectionId, taskId) => {
    let allSectionsCopy = JSON.parse(JSON.stringify(sections));
    let sectionIndex = allSectionsCopy.findIndex(
      section => section.id === sectionId
    );
    let sectionToUpdate = allSectionsCopy[sectionIndex];
    let taskIndex = sectionToUpdate.tasks.findIndex(task => task.id === taskId);
    let taskToUpdate = sectionToUpdate.tasks[taskIndex];

    taskToUpdate.desc = desc;

    setSections([...allSectionsCopy]);
  };

  const toggleTaskEditMode = (sectionId, taskId) => {
    let allSectionsCopy = JSON.parse(JSON.stringify(sections));
    let sectionIndex = allSectionsCopy.findIndex(
      section => section.id === sectionId
    );
    let sectionToUpdate = allSectionsCopy[sectionIndex];
    let taskIndex = sectionToUpdate.tasks.findIndex(task => task.id === taskId);
    let taskToUpdate = sectionToUpdate.tasks[taskIndex];

    taskToUpdate.editting = !taskToUpdate.editting;

    setSections([...allSectionsCopy]);
  };

  const keyRandomizer = () => {
    let randomKey = "";
    let characters =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < 5; i++) {
      randomKey += characters.charAt(
        Math.floor(Math.random() * characters.length)
      );
    }
    return randomKey;
  };

  return (
    <div className="App">
      <AddSectionForm addNewSection={addNewSection} />
      <ProjectSections
        sections={sections}
        addNewTask={addNewTask}
        updateTaskDesc={updateTaskDesc}
        toggleTaskEditMode={toggleTaskEditMode}
      />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

AddSectionForm.js

Just adds new sections to be used in the overall sections-state.

import React, { useState } from "react";

function AddSectionForm(props) {
  const [text, setText] = useState("");

  const handleChange = e => {
    setText(e.target.value);
  };

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

    props.addNewSection(text);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        onChange={handleChange}
        type="text"
        placeholder="Create Homepage"
        value={text}
      />

      <input className="add-section-button" type="submit" value="Add Section" />
    </form>
  );
}

export default AddSectionForm;

ProjectSections.js

Iterate over each section to initiate a unique Section component

import React from "react";
import Section from "./Section";

function ProjectSections(props) {
  const createSections = () => {
    const { sections, addNewTask, toggleTaskEditMode, updateTaskDesc } = props;

    return sections.map(section => {
      return (
        <Section
          section={section}
          addNewTask={addNewTask}
          updateTaskDesc={updateTaskDesc}
          toggleTaskEditMode={toggleTaskEditMode}
        />
      );
    });
  };

  return <div>{createSections()}</div>;
}
export default ProjectSections;

Section.js

Where all the magic happens.

import React, { useState } from "react";

const Section = ({
  section,
  addNewTask,
  toggleTaskEditMode,
  updateTaskDesc
}) => {
  const [newTaskName, setTaskName] = useState("");

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

    addNewTask(section.id, newTaskName);

    setTaskName("");
  };

  const handleUpdateTaskDesc = (e, sectionId, taskId) => {
    updateTaskDesc(e.target.value, sectionId, taskId);
  };

  const handleToggle = (sectionId, taskId) => {
    toggleTaskEditMode(sectionId, taskId);
  };

  const handleNewTaskChange = e => {
    setTaskName(e.target.value);
  };

  return (
    <div className="section">
      <h4>{section.sectionName}</h4>
      {section.tasks.map(task => {
        return !task.editting ? (
          <div className="task-item">
            <label>{task.desc}</label>
            <button onClick={() => handleToggle(section.id, task.id)}>
              Edit
            </button>
          </div>
        ) : (
          <div className="task-item">
            <input
              value={task.desc}
              onChange={e => handleUpdateTaskDesc(e, section.id, task.id)}
            />
            <button onClick={() => handleToggle(section.id, task.id)}>
              Done
            </button>
          </div>
        );
      })}

      {/* Add new task */}
      <form onSubmit={handleSubmit}>
        <input
          placeholder="Add Task"
          name="task"
          onChange={handleNewTaskChange}
          value={newTaskName}
        />
        <button type="submit" disabled={!newTaskName.length}>
          Add Task
        </button>
      </form>
    </div>
  );
};

export default Section;

Upvotes: 4

Related Questions