Unexpected Behavour When Removing An Item From An Array In React
Solution 1:
It might look like that it deletes the last element because you are using index
as identity.
When you use incrementing id
instead it makes much more sense
Example
const { Component, useState, useEffect } = React;
class Form_newPriority extends Component {
constructor(props) {
super(props);
this.state = {
listName: "",
counter: 1,
rows: [{ id: 1, fieldValue: "" }],
};
}
addrow = () => {
const rows = [...this.state.rows];
const counter = this.state.counter + 1;
rows.push({
id: counter,
fieldValue: "",
});
this.setState({ counter, rows });
};
removeRow = (idx) => {
const rows = [...this.state.rows];
rows.splice(idx, 1);
this.setState({ rows: rows });
};
render() {
return (
<div>
<input
className="w-full bg-white border border-alice px-4 py-2 mb-1 h-12 rounded-lg"
type="text"
></input>
<h2>Items:</h2>
{this.state.rows.map((row, idx) => {
return (
<React.Fragment key={row.id}>
<input
className="w-half bg-white border border-alice px-4 py-2 mb-4 mr-4 h-12 rounded-lg"
type="text"
placeholder={row.id}
></input>
<button
className="mb-4 text-red-700 inline-block"
onClick={() => {
this.removeRow(idx);
}}
>
Remove
</button>
</React.Fragment>
);
})}
<button
className="bg-alice hover:bg-gray-400 text-c_base text-sm font-bold py-1 px-2 rounded block mr-4"
onClick={this.addrow}
>
Add row
</button>
</div>
);
}
}
ReactDOM.render(
<Form_newPriority />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>
Solution 2:
This is a pretty common issue that is sometimes hard to wrap your head around. It has to do with how react manages dynamic elements like when returned from a map
.
The Problem
You're probably already seeing a warning that you should use a key
when returning from map
. When no key
is given, react automatically uses the index of the array element. This is the root of the issue.
React uses this key
to keep track of which element is which. So lets say you have a map that returns 3 divs. React will tag each of these divs with its key
so it knows what needs updating in the future. The result will be the following:
<div key={0} val='a' />
<div key={1} val='b' />
<div key={2} val='c' />
This works fine for adding and updating. If you update the entry at index 1
, react will update the second element by identifying it by key
.
The tricky scenario (like you have discovered) is deleting.
Let's say you delete the middle element. The resulting array will now only contain 2 positions. Now your JSX will look like this:
<div key={0} val='a' />
<div key={1} val='c' />
Maybe you can see what's happened already? You deleted the middle element, but to React, you have removed the last element and updated the second. Why? Because the key
is based on the index, and the index has changed. React will unmount the div
with a key of 2
, and keep the div
with key
of 1
. The second div
has not been removed, but has been re-rendered with a new prop instead.
The Solution
The root level fix is you need to provide a unique key
to each element that will not change over time. The implementation of this may differ based on your use case.
For instance, if your list is guaranteed to have a unique value for each entry, you can use that. This is the simplest solution, but isn't always available.
A solution I have used in the past is to keep a counter in state that increments every time an element is added. I then use the counter to make an id
for each element. That way, if an element is deleted, the id
will not change.
A Warning
A common mistake people make when faced with this problem is to generate a completely random key
for each element using a helper function. This might stop the current issue, but is a very bad practice that might lead to future bugs. If the key
changes every render, the element will unmount and remount every render. This won't have any side-effects in simple div
example like this, but if your element is another component with state, it will lose its state. If your element is an input, it might lose focus every update. It's tempting, but do not use a randomly generated number for the key
.
Post a Comment for "Unexpected Behavour When Removing An Item From An Array In React"