Reputation: 7
I'm totally new in React and had stucked with this error.
Error occurs because of this method:
deleteItem = (index) => {
this.state.items.splice(index, 1),
this.setState({
items: this.state.items
})
}
I also using @babel/plugin-proposal-class-properties
If I change button onClick event to this:
onClick={this.deleteItem.bind(index)}
it starts working perfectly and i dunno why.
Whole code example
import React from 'react';
export default class extends React.Component{
constructor(props) {
super(props)
this.state = {
items: [1, 2, 3, 4, 5]
};
};
addItem = () => {
this.state.items.push('added');
this.setState({
items: this.state.items
});
}
deleteItem = (index) => {
this.state.items.splice(index, 1),
this.setState({
items: this.state.items
})
}
render(){
const list = this.state.items.map((item, index) => {
return <li key={index}>{item}
<button onClick={this.deleteItem(index)}>Remove</button>
</li>
});
return (
<div>
<ul>
{list}
</ul>
<button onClick={this.addItem}>Add</button>
</div>
)
}
}
How can i fix this?
Upvotes: 0
Views: 2112
Reputation: 649
The error message refers to a common concept in functional programming - pure functions - which helps you write less buggy code. Pure functions have no side effects in that they don't assign new values to variables, and they don't mutate the state of the application. A famous example of a pure function is one that returns the square root of the argument. There's a test you can run in your mind: can you call the function multiple times with the same result/outcome? If it's a pure function, calling it multiple times does not change the result.
In your example, rendering the same content multiple times should be possible without any side effects. Rendering the content should not add or delete any items. If you want this pure function (render) to return a different result, you have to call it with different arguments which are the props and state.
Upvotes: 1
Reputation: 16908
As per the React docs:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
You are directly mutating the state
, don't do it. Pass a new state object in the setState()
method.
deleteItem = (index) => {
const newItems = this.state.items.filter((ele, idx) => idx != index),
this.setState({
items: newItems
})
}
Or you can use the Array.prototype.slice()
function, which does not modify the original array as it takes a copy of it in the given range and returns it in a new array:
deleteItem = (index) => {
const newItems = [...s.slice(0, index), ...s.slice(index + 1)],
this.setState({
items: newItems
})
}
Also the this
context is not set in your onClick
handler, use a arrow function () =>
to capture the lexical this:
onClick={() => this.deleteItem(index)}
That is necessary as this
points to the current execution context, if you don't bind this
to the lexical this
and when the callback is invoked the this
won't be referring to your component instance anymore.
Also if you invoke deleteItem(index)
immediately the handler will point to undefined
instead of the deleteItem
reference as the deleteItem(index)
returns undefined
.
You need to modify the addItem()
method as well, where you should form a new array from the added element:
addItem = () => {
this.setState({
items: this.state.items.concat('added')
});
}
Upvotes: 1
Reputation: 1219
We must not mutate states directly
Instead of
addItem = () => {
this.state.items.push('added');
this.setState({
items: this.state.items
});
}
Use
addItem = () => {
this.setState({
items: […this.state.items,'added']
});
}
And Instead of
deleteItem = (index) => {
this.state.items.splice(index, 1),
this.setState({
items: this.state.items
})
}
Use
deleteItem = (index) => {
this.setState(state => ({
items: state.items.filter((item, idx) => index !== idx)
}))
}
Use latest es6 or latter features of javascript as arrow function, object destructuring, spread operator and other
Upvotes: 0
Reputation: 10873
The problem is that you're mutating your state directly, use filter
instead:
deleteItem = (index) => {
this.setState(state => ({
items: state.items.filter((item, i) => index !== i)
}))
}
I've recently wrote a post about this and other issues when using React.
Also looking at your render code, when you do <button onClick={this.deleteItem(index)}>Remove</button>
, it will execute deleteItem
immediately and pass the result of that to onClick
, however onClick
expects a function. What you need to do is to wrap it in a function:
<button onClick={() => this.deleteItem(index)}>Remove</button>
Upvotes: 0