CynicalGoldfish
CynicalGoldfish

Reputation: 55

How do I delete a component from a React state array without deleting rest of array?

I have made a basic application to practice React, but am confused as to why, when I try to delete a single component from an state array, all items after it get deleted too. Here is my basic code:

App.js:

import React from 'react' 
import Parent from './Parent';
import './App.css';

function App() {
  return (
    <div className="App">
      <Parent />
    </div>
  );
}

export default App;

Parent.js:

import React, { useState } from 'react';
import ListItem from './ListItem';
import './App.css';

function Parent() {
  const [itemList, setItemList] = useState([])
  const [numbers, setNumbers] = useState([])

  const addItem = () => {
    const id = Math.ceil(Math.random()*10000)
    const newItem = <ListItem 
      id={id}
      name={'Item-' + id}
      deleteItem={deleteItem}
    />
    const list = [...itemList, newItem]
    setItemList(list)
  };

  const deleteItem = (id) => {
    let newItemList = itemList;
    newItemList = newItemList.filter(item => {
        return item.id !== id
    })
    setItemList(newItemList);
  }

  const addNumber = () => {
    const newNumbers = [...numbers, numbers.length + 1]
    setNumbers(newNumbers)
  }

  const deleteNum = (e) => {
    let newNumbers = numbers
    newNumbers = newNumbers.filter(n => n !== +e.target.innerHTML)
    setNumbers(newNumbers);
  }

  return (
    <div className="Parent">
      List of items:
      <div>
        {itemList}
      </div>
      <button onClick={addItem}>
        Add item
      </button>
      <div>
        List of numbers:
        <div>
          {numbers.map(num => (
            <div onClick={deleteNum}>{num}</div>
          ))}
        </div>
      </div>
      <button onClick={addNumber}>
        Add number
      </button>
    </div>
  );
};

export default Parent;

ListItem.js:

import React from 'react';
import './App.css';

function ListItem(props) {

  const { id, name, deleteItem } = props;

  const handleDeleteItem = () => {
    deleteItem(id);
  }

  return (
    <div className="ListItem" onClick={handleDeleteItem}>
      <div>{name}</div>
    </div>
  );
};

export default ListItem;
  1. When I add an item by clicking the button, the Parent state updates correctly.
  2. When I click on the item (to delete it), it deletes itself but also every item in the array that appears after it <-- UNWANTED BEHAVOUR. I only want to delete the specific item.
  3. I have tested it with numbers too (not creating a separate component). These delete correctly - only the individual number I click on is deleted.

As far as I can tell, the individual item components are saving a reference as to what the Parent state value was when they are created. This seems like very strange behaviour to me...

How do I delete only an individual item from the itemList state array when they are made up of separate components?

Thanks

EDIT: As per the instruction from Bergi, I fixed the issue by converting the 'itemList' state value to an array of objects to render (and rerender) when the list is changed instead:

    const addItem = () => {
      const id = Math.ceil(Math.random()*10000);
      const newItem = {
        id: id,
        name: 'Item-' + id,
      }
      const newList = [...itemList, newItem]
      setItemList(newList)
    }

...

    React.useEffect(() => {

    }, [itemList]);

...

<div className="Parent">
    List of items:
    <div>
        {itemList.map(item => {
        return (<ListItem
            id={item.id}
            name={item.name}
            deleteItem={deleteItem}
        />);
    })}
...

Upvotes: 3

Views: 155

Answers (1)

Bergi
Bergi

Reputation: 665100

The problem is that your deleteItem function is a closure over the old itemList, back from the moment in which the item was created. Two solutions:

  • use the callback form of setItemList
  • don't store react elements in that list, but just plain objects (which you can use as props) and pass the (most recent) deleteItem function only when rendering the ListItems

Upvotes: 2

Related Questions