Remove value from state (set new state) - javascript

I am trying to remove a value from my state.
I am using .filter as I believe this is the simplest way of doing it. I also want to implement an undo function ( but that's outside the scope of this question).
I have put this code in a sandbox
https://codesandbox.io/s/yrwo2PZ2R
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: x.movies,
};
}
remove = (e) => {
e.preventDefault();
console.log('remove movie.id:', e.target.value)
const index = e.target.value
this.setState({
movies: this.state.movies.filter((_, e) => e.id !== index)
});
}
render() {
return (
<div>
{this.state.movies.map(e =>
<div key={e.id}>
<li>{e.name} {e.id}</li>
<button value={e.id} onClick={this.remove}>remove</button>
</div>,
)}
</div>
);
}
}

Two problems.
First of all, the index you're getting from the event target value is a string, but you're comparing against a number. Change the index declaration as follows:
const index = Number(e.target.value);
Secondly, your filter is a little off. This will work:
this.state.movies.filter(movie => movie.id !== index)

The problem is index has string type, but id in objects has number type. You need type cast, for example:
const index = Number(e.target.value);
Other than that, you have some wrong _ in callback of filter function call. You don't need it. You need:
this.state.movies.filter(e => e.id !== index)
By the way I don't recommend to name values this way. Why e? You have array of movies. Use movie. Why index? You have id to remove. Then use idToRemove name.
You also have problem with adding items.
Firstly, you can add items like this:
this.setState({
movies: [...this.state.movies, { name: item.value.name, id: item.value.id }],
})
Another point: you have to autoincrement id. You can store last value in a variable. this.idCounter for example. And add will look like:
this.setState({
movies: [...this.state.movies, { name: item.value.name, id: this.idCounter++ }],
})
Example: https://codesandbox.io/s/2vMJQ3p5M

You can achieve the same in the following manner
remove = (e) => {
e.preventDefault();
console.log('remove movie.id:', e.target.value)
const index = e.target.value
var movies = [...this.state.movies]
var idx = movies.findIndex((obj) => obj.id === parseInt(index))
movies.splice(idx, 1);
this.setState({
movies
});
}
Also use parseInt to convert index to a string before comparing.
Directly setting the current state from the previous state values can cause problems as setState is asynchronous. You should ideally create a copy of the object and delete the object using splice method
CODESANDBOX

Related

Why is the first element getting removed too?

So I am new to javascript and I tried making a todo list. This works well with adding elements. The issue is when I am removing some item, the first one gets removed too, why is it so? I know I am missing a small thing and this may be really basic but I am not able to find out what that is.
const App1 = () => {
const [item, updatedItem]=useState('');
const [Items, setItems]=useState([]);
function inputEvent(event) {
updatedItem(event.target.value);
}
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return[
...prev,
item
]
});
updatedItem('');
}
let key=0;
return(<>
<div className='back'>
<div className='list'>
<header>ToDo List</header>
<form onSubmit={addItem}>
<input type='text' placeholder='Add an item' value={item} onChange={inputEvent}/>
<button type='submit'>+</button>
</form>
<div className='items'>
<ol>
{Items.map((val) => <li><button id={key++} onClick={(event) => {
setItems((Items) => {
return Items.filter((val, index) => {
if(index!==Number(event.target.id)){
return index;
}
}
);
});
key=0;
}}>x</button>{val}</li>)}
</ol>
</div>
</div>
</div>
</>);
You return the index in your filter, expecting this to always be true, yet 0 (the index of the first element) is a falsy value.
Try this instead:
return Items.filter((_val, index) => index !== Number(event.target.id));
Some unrelated code-quality notes:
In React, you should always set a key prop on each element when looping through them, rather than id.
map has a second argument, index, which it passes into the callback --- you don't have to keep track of this yourself with e.g. key++ etc.
If you use map's index parameter, then you can pass that directly into your filter rather than using Number(event.target.id), which is not very idiomatic in React.
If you don't use an argument of a callback, it's a good idea to prefix it with a _ (like I've done with _val here), to make it explicit that you're not using it.
Your filter callback should return a flag. index is a number. When treated as a flag, 0 is false (more on MDN). Instead:
return Items.filter((val, index) => index !== Number(event.target.id));
However, your code is returning an array of li elements without setting key on them (see: keys), which React needs in order to manage that list properly (you should be seeing a warning about it in devtools if you're using the development version of the libs, which is best in development). You can't use the mechanism you're using now for keys when doing that, it will not work reliably (see this article linked by the React documentation). Instead, assign each Todo item a unique ID when you create it that doesn't change, and use that as the key (and as the value to look for when removing the item):
// Outside the component:
let lastId = 0;
// Inside the component:
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return [
...prev,
{text: item, id: ++lastId}
];
});
updatedItem("");
};
// Add a remove function:
const removeItem = ({currentTarget}) => {
const id = +currentTarget.getAttribute("data-id"); // Get the ID, convert string to number
setItems(items => items.filter(item => item.id !== id));
};
// When rendering:
{Items.map((item) => <li key={item.id}><button data-id={item.id} onClick={removeItem}>x</button>{item.text}</li>)}
In some cases it may be useful to use useCallback to memoize removeItem to avoid unnecessary rendering, but often that's overkill.

How to delete an element from array in react?

I have two functions , one of them adds an item in array and the other one delete from that array using React JS (hooks).[Both are handler of click event].
What I have works incorrectly.
``id`` comes from ``contact.length`` and I deleted it with``contacts.splice(id, 1)``.
I dont have any idea why it has this problem.
it doesnt delete what would be clicked but a random one.
function handleAddRecord(nameValue, phoneValue) {
setContacts([...contacts , {
id : contacts.length,
name : nameValue,
phone : phoneValue
}])
}
function handleDelete(id) {
console.log("manager", id);
const newContacts = contacts.splice([id],1);
setContacts([...newContacts]);
}
One of the issue on the implementation is id generation keeping it array length could lead to issue as you delete and add elements there could be scenarios where there is same id for multiple items.
One of most widely used generator is uuid https://www.npmjs.com/package/uuid
Usage
const uuid = require("uuid");
uuid.v4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
Now use this in your implementation
Add Operation:
const handleAddRecord = (nameValue, phoneValue) => {
const newRecord = {
id: uuid.v4(), // This should be unique at all times
name: nameValue,
phone: phoneValue,
};
setContacts([...contacts, newRecord]);
};
Delete Operation:
Use filter rather than splice as for splice you'll need to find the index of the element with id. But with Filter it can be done is a single line
const handleDelete = (id) => {
setContacts(contacts.filter(item => item.id !== id));
};
Here we're assuming that id is the index of the element to be removed.
The splice function returns the removed elements, thus is not useful to take its result. Instead, make a copy of the array first, then remove the undesired element:
function handleDelete(id) {
console.log("manager", id);
const newContacts = [...contacts];
newContacts.splice(id,1);
setContacts(newContacts);
}
That's because splice alters the array itself.
More here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
Ok, id return index of current map?
Follow this example:
const assoc = [...contacts];
assoc.splice(id, 1);
setContacts(assoc);
You can delete the item by finding its index from array.
For Example:
function handleDelete(id) {
console.log("manager", id);
const index = contacts.findIndex((x) => x.id === id);
const newContacts = [
...contacts.splice(0, index),
...contacts.splice(index + 1),
];
setContacts(newContacts);
}
You need undestand, every time when i'll remove a item from a array of a index, that this index has use unique key... When React remove a item 6 (a example) this is remove of array first, and when react re-render function react can delete another component, because a array exist key 6, wehn you have more 6 item from array... Understand?
Follow a example:
import React, { useState } from 'react';
function User(data) { // data is a array
const [contacts, setContacts] = useState(data); // data return a list of contacts
/* contacts result a array object and has the following attributes
[{
name: 'Cael',
tel_number: '+55 11 9999-999',
e_mail: 'user#example.com',
! moment: "2021-06-15T05:09:42.475Z" // see this a date in ISO string
}]
*/
// about moment atribute:
// this atribute result when use `new Date().toISOString()`
// and this value is added in the moment that create a object in array list
// It's mean that every time result a unique key
const deleteFn = (val) => { // val result index of component and item array
const assoc = [...contacts];
assoc.splice(val, 1);
setContacts(assoc);
}
return (
<div>
{!!contacts.length &&
contacts.map((assoc, i) => { // variable i result in your id
const { moment, name, e_mail, tel_number } = assoc; // moment use a unique key
return (
<li key={moment}>
<span>{name}</span>
<span>{e_mail}</span>
<span>{tel_number}</span>
<button type="button" onClick={() => deleteFn(i)}>Delete</button>
</li>
);
})}
</div>
);
}
export default User;
I hope, this helpfull you!

React.JS: Add and Remove field dynamically

I have in my state:
this.state = { inputs: ['0'] }
In my render :
{this.state.inputs.map((input, i) => <Travel formName={FORM_NAME} number={input}/>)}
<button onClick={ () => this.appendInput() }> CLICK ME TO ADD AN INPUT </button>
<button onClick={ () => this.deleteInput() }> Delete </button>
Now when I click on this.appendInput() I add a field like this:
appendInput() {
var newInput = `${this.state.inputs.length}`;
console.log("`${this.state.inputs.length}`" , `${this.state.inputs.length}`)
this.setState(prevState => ({ inputs: prevState.inputs.concat([newInput]) }));
console.log("this.state.inputs add ", this.state.inputs)
this.forceUpdate();
}
But I don't understand how can I do with the deleteInput, how can I delete the last field in this way?
EDIT:
I have tried like this:
deleteInput(){
var newInput = this.state.inputs.pop();
this.setState( ({ inputs: newInput }));
this.forceUpdate();
}
but then I receive the message:
_this.state.inputs.map
write your delete function like this, you were assigning popped data from the array.
deleteInput(){
var newInput = [...this.state.inputs];
newInput.pop();
this.setState( ({ inputs: newInput }));
this.forceUpdate();
}
pop() will return the removed element, which you don't want to set your state as. Doing this will set your state to the single element that was just removed. This is what causes your error, as you're trying to call map() on an object which is not a list.
Instead, your deleteInput() should do this:
this.setState(state => state.inputs.slice(0,-1))
This will properly remove the last element of your inputs array from your state.
Additionally, as a side note, calling setState() automatically queues a rerender, so calling this.forceUpdate() is unnecessary and inefficient.
You should be able to use .pop() to remove the last item in your inputs array
This thread has some more detailed answers about pop and slice: Remove last item from array

How to use setState to splice into an array in the state?

My state.events is in array that is made up of the component instance: EventContainer.
I want my setState to place a new EventContainer in the state.events array. However, I want that EventContainer to go in the index immediately after the specific EventContainer that made the setState call.
I'm looking for help with making the adjustments necessary to my approach or, if my entire approach is bad, a recommendation on how to go about this. Thank you very much.
I'm developing an itinerary builder which is made up of rows/EventContainers that represent an activity on a given day.
Each EventContainer has a button that needs to offer the user the ability to onClick an additional row immediately after that EventContainer.
class DayContainer extends React.Component {
constructor(props){
super(props);
this.state = {
events: [],
};
this.pushNewEventContainerToState = this.pushNewEventContainerToState.bind(this);
}
pushNewEventContainerToState (index){
let newEvent = <EventContainer />;
this.setState(prevState => {
const events = prevState.events.map((item, j) => {
if (j === index) {
events: [...prevState.events.splice(index, 0, newEvent)]
}
})
})
}
render(){
return (
<>
<div>
<ul>
{
this.state.events === null
? <EventContainer pushNewEventContainerToState= .
{this.pushNewEventContainerToState} />
: <NewEventButton pushNewEventContainerToState={this.pushNewEventContainerToState} />
}
{this.state.events.map((item, index) => (
<li
key={item}
onClick={() =>
this.pushNewEventContainerToState(index)}
>{item}</li>
))}
</ul>
</div>
</>
)
}
}
My goal in setState was to splice newEvent into this.state.events immediately after the index (the parameter in pushNewEventContainerToState function).
I'm getting this error but I'm guessing there's more going on than just this: Line 23:22: Expected an assignment or function call and instead saw an expression no-unused-expressions.
I can see at least 2 issues with the code.
- Splice will mutate the array in place
- You are not returning the updated state.
You can instead use slice to build the new array.
pushNewEventContainerToState(index) {
let newEvent = < EventContainer / > ;
this.setState(prevState => {
const updatedEvents = [...prevState.events.slice(0, index], newEvent, ...prevState.events.slice(index + 1];
return {
events: updatedEvents
})
})
}
As I'm fairly new to coding, it took me awhile but I was able to compile the full answer. Here is the code, below. Below that, I explain, point by point, what the problem was and how the updated code addresses that.
class DayContainer extends React.Component {
constructor(props){
super(props);
this.state = {
events: [{key:0}],
};
this.pushNewEventContainerToState = this.pushNewEventContainerToState.bind(this);
}
pushNewEventContainerToState(index) {
let newEvent = {key: this.state.events.length};
this.setState(prevState => {
let updatedEvents = [...prevState.events.slice(0, index + 1), newEvent, ...prevState.events.slice(index + 1)];
return {
events: updatedEvents
};
})
}
render(){
return (
<>
<div>
<ul>
{this.state.events.map((item, index) => (
<li key={item.key}>
< EventContainer pushNewEventContainerToState={() => this.pushNewEventContainerToState(index) } / >
</li>
))}
</ul>
</div>
</>
)
}
}
Setup
Starting with state.events, instead of starting with an empty array, I'm starting with one object, including a key starting at 0, because I always want the user to start with one EventContainer.
Regarding pushNewEventContainerToState, #Sushanth made a great recommendation. Please refer directly to that function in my latest code. The refinement I made has to do with the way I separate the EventContainer being passed to this.state.events. I've moved the EventContainer from pushNewEventContainerToState down to the render() element. I've given it a prop of key={item.key} and wrapped the component instance in a li. The very first EventContainer will have a key of 0 (see state.events[0]). Now, each new EventContainer passed to state.events will have a key that's based off the latest .length() of the state.events array (refer to the latest value of the let newEvent variable in pushNewEventContainerToState).
All of that allowed me to fix a big problem I was facing: I needed the newest EventContainer to be placed in the index immediately after the index of the EventContainer calling pushNewEventContainerToState. The main reason this was happening was because I wasn't properly passing the index to the EventContainer inside of render(). Now that I have the actual EventContainer there, I can pass it a prop in the right manner (please refer EventContainer's prop in render). Now I'm calling pushNewEventContainerToState with the correct index.

How to Delete individual items from list in react

I have built a simple ToDo App. Rending the tasks from the form input is working fine, but I am unable to delete the tasks when clicked on Delete button.
export class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
task:'',
items:[]
}
}
onChangeHandler=(e)=>{
this.setState({
[e.target.name]: e.target.value
})
}
addItem=(e)=>{
e.preventDefault()
if (this.state.task!==""){
this.setState({
items:[...this.state.items,this.state.task],
task:''
})
}
}
removeItem=(index)=>{
const remainingItems = this.state.items.filter(j => {
return j !== index
})
this.setState({
items: remainingItems
})
};
render() {
return (
<div>
<form>
<input type='text' name="task"onChange={this.onChangeHandler} value={this.state.task} placeholder='Enter Task'/>
<button type='submit' onClick={this.addItem}>Add Task</button>
</form>
<Lists items={this.state.items}
delete={this.removeItem}/>
</div>
)
}
}
export class Lists extends Component {
removeItems=(index)=>{
this.props.delete(index)
}
render() {
return (
<div>
{this.props.items.map((item,index)=>
<li className="Lists" key={index}>{item}
<button type='button' onClick={this.removeItems(index)}>Remove</button>
</li>)}
</div>
)
}
}
Do you even happen to have any items to delete here or the list comes up empty? Delete function itself looks fine but you have couple of problems here.
Don't use index as a key. In case you're reordering or deleting (which you are doing) an array of items, you can run into a lot of issues. Here's a good article: https://medium.com/#vraa/why-using-an-index-as-key-in-react-is-probably-a-bad-idea-7543de68b17c
Probably the error is with this since you're deleting key which, since it's an iterator, is reassigned to another element when array repopulates itself. Change iterator to some other unique identifier for each element.
You're calling removeItems method as soon as it's set. If you have invoked methods (with ()) inside return of render, it will be executed immediately on each refresh. That's why I'm asking do you have anything to delete at all since, if delete function is okay written, this would probably delete all items as soon as they are added.
Best method would be to use dataset. To each element you can add dataset like this:
data-item-id={some-id} and you can fetch it inside your method from the fired event like this const clickedId = event.currentTarget.dataset.someId. Note that dataset in the element must be written like-this, and it's rewritten automatically when fetching it into camelCase (likeThis). Then you can use this index to target the element you want inside the array and delete it.
Note that the iterator issue still applies, and you need a different unique identifier.
Let me know if you need further explanation.
You can delete the current item using splice method.
removeItem = index=> {
let newArray = this.state.items;
newArray.splice(index, 1);
this.setState({
items: newArray
});
};
It would be better to use onClick for removing item like this :
<button type='button' onClick={()=>this.removeItems(index)}>Remove</button>
Hope this helps.
I prefer to pass item that i would like to remove, index can be decieving becouse it changes.
Find index by unique key, i use item.id as unique key.
removeItem = item => {
const items = this.state.items;
// if using lodash i use findIndex
const index = _.findIndex(items, i => i.id === item.id)
// if plain js
const index = items.findIndex(i => i.id === item.id)
items.splice(index, 1);
this.setState({
items
});
};

Categories