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);
};
...
};