2

As a fairly new addict to programming, I'm stuck with a simple app that lists items. Adding items, is OK. But as for deleting one item, I just can't figure out how to implement this function. Now on clicking an item the whole array of items is deleted. I have tried to put the deleteItem function in other files, but I seem to have mixed up the references.

ItemContext.js:

import React, { useState, createContext } from 'react';
import { v1 as uuidv1 } from 'uuid';
export const ItemContext = createContext();

export const ItemProvider = (props) => {
  const [items, setItems] = useState([
    {
      name: "Red Beans",
      amount: 23,
      id: uuidv1()
    },
    {
      name: "Nuts for Bunnies",
      amount: 33,
      id: uuidv1()
    },
    {
      name: "Chopped Tomatoes",
      amount: 2,
      id: uuidv1() 
    }
]);
  return ( 
      <ItemContext.Provider value={[items, setItems]} >
        {props.children}
      </ItemContext.Provider>
   );
}

AddItem.js:

    import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';

const AddItem = () => {
  const [name, setName] = useState('');
  const [amount, setAmount] = useState('');
  const [items, setItems] = useContext(ItemContext);

  const updateName = (e) => {
    setName(e.target.value)
  }

  const updateAmount = (e) => {
    setAmount(e.target.value)
  }

  const addItem = (e) => {
    e.preventDefault();
    setItems(prevItems => [...prevItems, {name: name, amount: amount, key: items.id}])
  }

  return ( 
      <form className="new-item" onSubmit={addItem}>
        <input type="text" name="name" value={name} onChange={updateName} placeholder="Add a new item"/>
        <input type="text" name="amount" value={amount} onChange={updateAmount} placeholder="Amount"/>
        <button>Submit</button>
      </form>
   );
}
 
export default AddItem;

Item.js:

import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';

const Item = ({name, amount, /*deleteItem*/ }) => {
  const [id, setId] = useState('');
  const [items, setItems] = useContext(ItemContext);

  const deleteItem = (id) => {
    // const newItems = items.filter(item => items.id !== id);
    // setItems(newItems);
    const newItems = Object.assign([], ...items)
    items.splice(id, 1);
    setItems(newItems);
  }

  return (
    <div>
      <h3>{name}</h3>
      <p>{amount}</p>
      <button onClick={() => deleteItem(items.id)} className="delete-btn">Delete</button>
    </div>
  );
}
 
export default Item;

ItemsList.js:

import React, { useState, useContext } from 'react';
import Item from './Item';
import { ItemContext } from './ItemContext';
import { v1 as uuidv1 } from 'uuid';

const ItemsList = () => {
  const [items, setItems] = useContext(ItemContext);
  const [id, setId] = useState('');

  const deleteItem = (id) => {
    // const newItems = items.filter(item => items.id !== id);
    // setItems(newItems);
    const newItems = Object.assign([], ...items)
    items.splice(id, 1);
    setItems(newItems);
  }

  return (
    <div className="items-list">
      {items.map(item => (
        <Item name={item.name} amount={item.amount} key={uuidv1()} deleteItem={deleteItem}/>
      ))}
    </div> 
   );
}
 
export default ItemsList;
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 2
    `items.filter(item => items.id !== id)` should be sufficient for removing an element from array. You seem to have a lot of duplicated state and multiple delete handlers, this may be part of your problem. Which "state" is the real `items` state? Oh, wait, I see, you are pulling the `items` "state" from the context. – Drew Reese Mar 11 '21 at 06:33

1 Answers1

2

Initially you need to provide a valid initial value to the ItemContext context you are creating. Its signature should match what the context value will be. I suggest using an object instead of an array.

export const ItemContext = createContext({
  items: [],
  addItem: () => {},
  deleteItem: () => {}
});

Then define addItem and deleteItem handlers to include in the context value. You want to avoid exposing out the setItems state function directly so ItemProvider maintains control over the state invariant.

const initialState = [
  {
    name: "Red Beans",
    amount: 23,
    id: uuidv1()
  },
  {
    name: "Nuts for Bunnies",
    amount: 33,
    id: uuidv1()
  },
  {
    name: "Chopped Tomatoes",
    amount: 2,
    id: uuidv1()
  }
];

const ItemProvider = (props) => {
  const [items, setItems] = useState(initialState);

  const addItem = (item) => setItems((items) => [...items, item]);
  const deleteItem = (id) =>
    setItems((items) => items.filter((item) => item.id !== id));

  const value = {
    items,
    addItem,
    deleteItem
  };

  return (
    <ItemContext.Provider value={value}>
      {props.children}
    </ItemContext.Provider>
  );
};

Then destructure the ItemContext value where you are consuming it.

AddItem.js - Make sure to assign a new uuidV1 GUID for new items. I think this was your biggest issue, you were assigning undefined to a key property (key: items.id) instead of creating a new GUID for the id property.

import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';

const AddItem = () => {
  const [name, setName] = useState("");
  const [amount, setAmount] = useState("");
  const { addItem } = useContext(ItemContext);

  const updateName = (e) => {
    setName(e.target.value);
  };

  const updateAmount = (e) => {
    setAmount(e.target.value);
  };

  const submitHandler = (e) => {
    e.preventDefault();
    addItem({
      name: name,
      amount: amount,
      id: uuidv1() // <-- new GUID here!!
    });
  };

  return (
    <form className="new-item" onSubmit={submitHandler}>
      <input
        type="text"
        name="name"
        value={name}
        onChange={updateName}
        placeholder="Add a new item"
      />
      <input
        type="text"
        name="amount"
        value={amount}
        onChange={updateAmount}
        placeholder="Amount"
      />
      <button>Submit</button>
    </form>
  );
};

Item.js - Get deleteItem from the context and ensure to pass item id to component.

import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';

const Item = ({ name, amount, id }) => {
  const { deleteItem } = useContext(ItemContext);

  return (
    <div>
      <h3>{name}</h3>
      <p>{amount}</p>
      <button onClick={() => deleteItem(id)} className="delete-btn">
        Delete
      </button>
    </div>
  );
};

ItemsList.js - Set the React key to the current item id and also pass the id as a prop to `Item.

import React, { useState, useContext } from 'react';
import Item from './Item';
import { ItemContext } from './ItemContext';

const ItemsList = () => {
  const { items } = useContext(ItemContext);

  return (
    <div className="items-list">
      {items.map((item) => (
        <Item
          key={item.id} // <-- Static React key to item
          id={item.id} // <-- Pass id as prop for delete handler to use
          name={item.name}
          amount={item.amount}
        />
      ))}
    </div>
  );
};

Demo

Edit deleting-items-in-reactjs-using-usestate-and-usecontext

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks for your effort, really appreciate it. Now its much clearer for me how to structure this. – Viktor Helfrih Mar 11 '21 at 16:22
  • I tried to connect this project to a firebase database. But somehow I am still facing issues eg. the state is not updated immediately when I add a new item, and the delete function is not working. I tried copying the item component into the itemslist component, without success. I found out that I made an infinite loop in the useEffect. Now I fixed it, but not sure if it is ok. I just can't beleive that I cannot find the solution for a properly working simple app as this. Or perhaps I should add more useEffects somewhere. If you have the time can you look at my code if I upload it? – Viktor Helfrih Apr 25 '21 at 18:07