Skip to content
Advertisement

How do I add an list element seperately to 2 lists?

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;

Code Sandbox Link

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement