Reputation: 744
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
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.
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);
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;
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;
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