Home > Software design >  Why doesn't my UI properly rerender my deleted object?
Why doesn't my UI properly rerender my deleted object?

Time:06-29

I have a parent component that maps over a list state using an Input component

import "./styles.css";
import { useState, useEffect } from "react";
import Input from "./Input";

export default function App() {
  const [list, setList] = useState([{}]);
  useEffect(() => {
    console.log("list", list);
  }, [list]);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      {list.map((_, i) => (
        <div key={i}>
          <Input id={i} setList={setList} />
        </div>
      ))}
    </div>
  );
}

Submitting each Input leaves another Input available for submission. However, when I delete each Input, I see that the data object is gone but the UI doesn't reflect this.

import { useState } from "react";

const Input = ({ setList, id }) => {
  const [value, setValue] = useState("");
  const [submitted, setSubmitted] = useState(false);
  const handleChange = (event) => {
    const result = event.target.value.replace(/\D/g, "");
    setValue(result);
  };
  const handleSubmit = () => {
    setList((list) => [...list, { id: id, total: value }]);
    setSubmitted(true);
  };
  const handleDelete = () => {
    console.log("id", id);
    setList((list) => list.filter((item) => item.id !== id));
  };
  return (
    <div>
      <input
        value={new Intl.NumberFormat("ko-KR", {
          style: "currency",
          currency: "KRW"
        }).format(Number(value))}
        onChange={handleChange}
        type="text"
      />
      {submitted ? (
        <button disabled={value.length === 0} onClick={handleDelete}>
          Delete
        </button>
      ) : (
        <button disabled={value.length === 0} onClick={handleSubmit}>
          Submit!
        </button>
      )}
    </div>
  );
};

export default Input;

I suspect that this is because

  const [list, setList] = useState([{}])

Is initialized with an empty object, and that is causing the problem. However, I need at least one object inside the list state so I can have the initial Input component for submission.

How do I properly delete my Input component?

My codesandbox

CodePudding user response:

Your issue is with your key

<div key={i}></div>

React uses the key prop to help identify what elements have been changed. Keys should remain the same for each element in your array across re-renders, so that React can identify when an element has been updated based on a state change. As you're removing elements from your state, the index doesn't serve as a stable key that is uniquely associated with your object (as objects shift into lower indexes when one is removed). Instead, it's best to add some sort of id associated with the elements that you render:

const [list, setList] = useState([{ id: 0}]);
...
{list.map(({ id }) => (
  <div key={id}>
    <Input id={id} setList={setList} />
  </div>
))}
...

Then in your component where you add your objects, you can create a new id (which doesn't change) for each new object. Below I've used Math.random(), but you should use something more reliable than that (eg: uuidv4).

setList((list) => [...list, { id: Math.random(), total: value }]);

You should also look into how you're adding new values. The "submit" Input only appears because of the new object that you're adding to your list: { id: Math.random(), total: value }. I would instead have App display one Input which is primarily responsible for adding items to your list state, and then have each Input that is rendered within the .map() take care of the "delete" functionality.

CodePudding user response:

This happen because you used index as a key in map.

You can read more about it here.

Long story short, for example you have 3 items, you delete second that has key "2", then your third element receive a key "2" and react think that this is the same element.

I updated your example with item template that have temp id set to "new item" and used id as a key.

Also I create id for each new element in handleSubmit function, so your temp id never became a real one.

Here is updated code. I removed some code that wasn't changed.

App component

const TEMPLATE = {
  id: "new item",
  value: 0,
}

export default function App() {
  const [list, setList] = useState([{...TEMPLATE}]);

  return (
    <div className="App">
      {list.map((item) => (
        <div key={item.id}>
          <Input id={item.id} setList={setList} />
        </div>
      ))}
    </div>
  );
}

And here is Input component. You would want to generate Id in some other way.

const Input = ({ setList, id }) => {
  ...
  const handleSubmit = () => {
    setList((list) => [...list, { id: Math.random(), total: value }]);
    setSubmitted(true);
  };
  ...    
};
  • Related