I am pretty new to react. So I have one parent component which has two child components. These 2 children are the lists that should be displayed. So far I figured out how to transfer the data between two lists by checking the status property of the data. I am not able to understand how to add data into the separate lists and edit them since the parent component renders the 2 lists. Can anyone explain how to add and edit new data that the user will enter? Should I create new states and props on the Items page or should I create them on the child component page? I am pretty confused.
import React,{useState,useEffect} from 'react' import { Completed } from './Completed' import { Pending } from './Pending' export const Items = () => { const [items,setItems]=useState([ { id: 1, title:'Workout', status:'Pending' }, { id: 2, title:'Read Books', status:'Pending' }, { id: 3, title:'Cook Pizza', status:'Pending' }, { id: 4, title:'Pay Bills', status:'Completed' }, { id: 5, title:' Watch Big Short', status:'Completed' }, { id: 6, title:' Make nutrition Plan', status:'Pending' } ]) const updateStatus=(id,newStatus)=>{ let allItems=items; allItems=allItems.map(item=>{ if(item.id===id){ console.log('in here') item.status=newStatus; } return item }) setItems(allItems) } return ( <div class="items"> <Pending items={items} setItems={setItems} updateStatus={updateStatus}/> <Completed items={items} setItems={setItems} updateStatus={updateStatus}/> </div> ) } import React from 'react' export const Pending = ({items,setItems,updateStatus}) => { return ( <div className="pending"> <h1>LEFT</h1> { items && items.map(item=>{ if(item && item.status==='Pending') return <><p className="item" key={item.id}>{item.title} <button className="mark_complete" key={item.id} onClick={()=>{updateStatus(item.id,'Completed')}}>Move Right</button></p></> }) } </div> ) } import React from 'react' export const Completed = ({items,setItems,updateStatus}) => { return ( <div className="completed"> <h1>RIGHT</h1> <form onSubmit={this.addItem}> <input placeholder="enter task"> </input> <button type="submit">add</button> </form> { items && items.map(item=>{ if(item && item.status==='Completed') return <><p className="item" key={item.id}>{item.title} <button className="mark_pending" key={item.id} onClick={()=>{updateStatus(item.id,'Pending')}}> Move Left</button></p> </> }) } </div> ) }
I have attached the 3 components which are Items, Pending and Completed above.
Advertisement
Answer
It’s almost always better to have the state in the parent and pass down props to the children. So you want to keep your items
state where it is. You can create an addItem
function and pass it down as a prop to any child.
I don’t think it makes sense to be able to add items from both lists since new items should be 'Pending'
. So I would recommend that you put your add form in a new component AddItem
which would be a third child of Items
. Once AddItem
calls the addItem
function from props, that item will get saved to the state in items
and it will show up in the Pending
list automatically.
If all new items have status 'Pending'
then the only information that we should need to add an item is the title
of the task.
This function goes in Items
:
const addItem = (title) => { // set state using a callback function of current state setItems((current) => { // the highest number of all current ids, or 0 if empty const maxId = current.reduce((max, o) => Math.max(max, o.id), 0); // the next id is the max plus 1 const id = maxId + 1; // add new item to the current - concat won't mutate the array return current.concat({ id, title, status: "Pending" }); }); };
Your AddItem
component uses a controlled input
to create the text for the new item.
export const AddItem = ({ addItem }) => { const [title, setTitle] = useState(""); const handleSubmit = (e) => { // prevent form submission from reloading the page e.preventDefault(); // call the addItem function with the current title addItem(title); // clear the form setTitle(""); }; return ( <form onSubmit={handleSubmit}> <input placeholder="enter task" value={title} onChange={(e) => setTitle(e.target.value)} /> <button type="submit">add</button> </form> ); };
Inside the return
of Items
, include your form:
<AddItem addItem={addItem} />
Unrelated to the question at hand, there are a few other improvements that you can make to your code.
Your updateStatus
function actually mutates the current item
. You should instead create a new object for the changed item by copying everything except the status
.
You are getting warnings about unique keys because the key must be on the outermost component inside the .map()
. You put a fragment <>
outside the <p>
which has the key
, so remove the fragment.
In my opinion the filtering of which item goes in each list should be done by the parent. Your Completed
and Pending
components are extremely similar. You should combine them into one component. Everything that is different between the two, such as texts and class names, can be controlled by the props that you pass in.
import React, { useState } from "react"; export const ItemsList = ({ items, title, className, buttonText, onClickButton }) => { return ( <div className={className}> <h1>{title}</h1> {items.map((item) => ( <p className="item" key={item.id}> <span className="item_title">{item.title}</span> <button className="move_item" key={item.id} onClick={() => { onClickButton(item.id); }} > {buttonText} </button> </p> ))} </div> ); }; // example of how to compose components // this keeps the same setup that you had before, but without repeated code export const Completed = ({ items, updateStatus }) => { return ( <ItemsList title="RIGHT" buttonText="Move Left" className="completed" items={items.filter((item) => item.status === "Completed")} onClickButton={(id) => updateStatus(id, "Pending")} /> ); }; export const AddItem = ({ addItem }) => { const [title, setTitle] = useState(""); const handleSubmit = (e) => { // prevent form submission from reloading the page e.preventDefault(); // call the addItem function with the current title addItem(title); // clear the form setTitle(""); }; return ( <form onSubmit={handleSubmit}> <input placeholder="enter task" value={title} onChange={(e) => setTitle(e.target.value)} /> <button type="submit">add</button> </form> ); }; export const Items = () => { const [items, setItems] = useState([ { id: 1, title: "Workout", status: "Pending" }, { id: 2, title: "Read Books", status: "Pending" }, { id: 3, title: "Cook Pizza", status: "Pending" }, { id: 4, title: "Pay Bills", status: "Completed" }, { id: 5, title: " Watch Big Short", status: "Completed" }, { id: 6, title: " Make nutrition Plan", status: "Pending" } ]); const addItem = (title) => { // set state using a callback function of current state setItems((current) => { // the highest number of all current ids, or 0 if empty const maxId = current.reduce((max, o) => Math.max(max, o.id), 0); // the next id is the max plus 1 const id = maxId + 1; // add new item to the current - concat won't mutate the array return current.concat({ id, title, status: "Pending" }); }); }; const updateStatus = (id, newStatus) => { setItems((current) => // arrow function without braces is an implicit return current.map((item) => item.id === id ? // copy to new item if id matches { ...item, status: newStatus } : // otherwise return the existing item item ) ); }; return ( <div className="items"> <AddItem addItem={addItem} /> {/* can set the props on ItemsList here */} <ItemsList title="LEFT" buttonText="Move Right" className="pending" items={items.filter((item) => item.status === "Pending")} // create a function that just takes the `id` and sets the status to "Completed" onClickButton={(id) => updateStatus(id, "Completed")} /> {/* or do it in a separate component */} <Completed items={items} updateStatus={updateStatus} /> </div> ); }; export default Items;