Home > Software engineering >  Why when you delete an element out of react array, the inner element you pass it to remains
Why when you delete an element out of react array, the inner element you pass it to remains

Time:09-05

In a react component I have an array of things. I iterate through that array display the name of the thing in a plain div, then pass each element to another component to display details.

What's happening: if I delete an element from anywhere except the bottom (last array element) the header that is displayed in the main element containing the array is correct (the one I clicked "delete" on disappeared), but the "body" (which is another component) remains. Instead, the inner component is acting as if I deleted the last element of the array and kind of "moves" up the array.

It's hard to describe in words. See example below. Delete the top element or one of the middle ones and see how the header for the section starts not matching the contents.

I'm trying to understand why this is happening.

Example code (delete the middle element of the array and see what happens): https://codesandbox.io/s/confident-buck-dodvgu?file=/src/App.tsx

Main component:

import { useState, useEffect } from "react";
import InnerComponent from "./InnerComponent";
import Thing from "./Thing";

import "./styles.css";

export default function App() {
  const [things, setThings] = useState<Thing[]>([]);

  useEffect(() => resetThings(), []);

  const resetThings = () => {
    setThings([
      { name: "dog", num: 5 },
      { name: "cat", num: 7 },
      { name: "apple", num: 11 },
      { name: "book", num: 1}
    ]);
  };

  const onDeleteThing = (indexToDelete: number) => {
    const newThings = [...things];
    newThings.splice(indexToDelete, 1);
    setThings(newThings);
  };

  return (
    <div className="App">
      {things.map((thing, index) => (
        <div key={`${index}`} className="thing-container">
          <h2>{thing.name}</h2>
          <InnerComponent
            thing={thing}
            index={index}
            onDelete={onDeleteThing}
          />
        </div>
      ))}
      <div>
        <button onClick={resetThings}>Reset Things</button>
      </div>
    </div>
  );
}

Inner component:

import { useEffect, useState } from "react";
import Thing from "./Thing";

interface InnerComponentParams {
  thing: Thing;
  index: number;
  onDelete: (indexToDelete: number) => void;
}

export const InnerComponent: React.FC<InnerComponentParams> = ({
  thing,
  index,
  onDelete
}) => {
  const [name, setName] = useState(thing.name);
  const [num, setNum] = useState(thing.num);

  return (
    <div>
      <div>Name: {name}</div>
      <div>Num: {num}</div>
      <div>
        <button onClick={(e) => onDelete(index)}>Delete Me</button>
      </div>
    </div>
  );
};

export default InnerComponent;

CodePudding user response:

You are creating unnecessary states in the child component, which is causing problems when React reconciles the rearranged Things. Because you aren't setting the state in the child component, leave it off entirely - instead, just reference the prop.

export const InnerComponent: React.FC<InnerComponentParams> = ({
  thing,
  index,
  onDelete
}) => {
  return (
    <div>
      <div>Name: {thing.name}</div>
      <div>Num: {thing.num}</div>
      <div>
        <button onClick={(e) => onDelete(index)}>Delete Me</button>
      </div>
    </div>
  );
};

The other reason this is happening is because your key is wrong here:

{things.map((thing, index) => (
  <div key={`${index}`}

Here, you're telling React that when an element of index i is rendered, on future renders, when another element with the same i key is returned, that corresponds to the JSX element from the prior render - which is incorrect, because the indicies do not stay the same. Use a proper key instead, something unique to each object being iterated over - such as the name.

<div key={thing.name}

Using either of these approaches will fix the issue (but it'd be good to use both anyway).

CodePudding user response:

This is also wrong. You're removing everything except the index.

const onDeleteThing = (indexToDelete: number) => {
    const newThings = [...things];
    newThings.splice(indexToDelete, 1);
    setThings(newThings);
  };

Use filter:

  const onDeleteThing = (indexToDelete: number) => {
    const newThings = [...things].filter(
      (thing, index) => index !== indexToDelete
    );

    setThings(newThings);
  };
  • Related