I am trying to remove an item from a list of items, but it does not seem to work. I have a page where I can add entries dynamically and items can be removed individually too. Adding seems to just work fine.
Sandbox: https://codesandbox.io/s/angry-heyrovsky-r7b4k
Code
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
values: []
};
}
onChange = event => {
this.setState({ value: event.currentTarget.value });
};
onAdd = () => {
this.setState({
value: "",
values: [...this.state.values, this.state.value]
});
};
onRemove = index => {
console.log(index);
let { values } = this.state;
let filteredIDs = values.splice(index, 1);
this.setState({
values: filteredIDs
});
};
render() {
let { values, value } = this.state;
return (
<>
<input
required
placeholder="xyz#example.com"
value={value}
onChange={this.onChange}
/>
<button onClick={this.onAdd}>Add</button>
<div>
<ul className="email-list-holder wd-minus-150">
{values.map((value, index) => (
<li key={index}>
{value}
<button
onClick={() => this.onRemove(index)}
style={{ cursor: "pointer" }}
>
Remove
</button>
</li>
))}
</ul>
</div>
</>
);
}
}
let filteredIDs = values.splice(index, 1); returns the removed item after it removes it from values
you'll want
onRemove = index => {
let { values } = this.state;
values.splice(index, 1);
this.setState({
values
});
tested and works on your codesandbox :p
Here is the working demo for you
https://codesandbox.io/s/purple-snow-kkudc
You have to change the below line.
let filteredIDs = values.splice(index, 1);
Use it instead of above one.
let filteredIDs = values.filter((x, i)=> i!==index);
Hope this will work for you.
I think you are using wrong javascript method when remove the item.
Splice method changes the contents of an array by removing or replacing existing elements and/or adding new elements
Slice method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included) where begin and end represent the index of items in that array. The original array will not be modified.
Replace
let filteredIDs = values.splice(index, 1);
With
let filteredIDs = values.slice(index, 1);
You are setting the removed part of the array instead of the correct one.
onRemove = index => {
console.log(index);
let { values } = this.state;
values.splice(index, 1);
this.setState({
values
});
};
This should work.
You set the removed items as the new values. This will fix it.
onRemove = index => {
console.log(index);
let { values } = this.state;
let filteredIDs = values.splice(index, 1);
this.setState({
values: values
});
};
splice returns the deleted elements and you are setting the removed elements. You can directly do:
values.splice(index, 1);
this.setState({
values,
})
You can also use uniqueId in order to give each new element a uniqueId this would help in filtering logic.
Here's how I may have structured the state and methods:
this.state = {
values: {
todo1: {
value: 'a'
},
todo2: {
value: 'b'
},
}
}
// Addition
this.setState({
values: {
...this.state.values,
uniqueId: {
value: 'New Value from input'
}
}
});
// Deletion
const stateValues = this.state.values;
delete stateValues[uniqueId];
this.setState({
values: stateValues,
});
Related
import React, { Component } from 'react'
import Item from './components/item'
import './App.css'
class App extends Component {
state = { item: "", array: [], check: false }
setItem = (event) => {
this.setState({
item: event.target.value
})
}
add = () => {
let item = this.state.item;
if (item != "") {
let arr = []
arr.push(item)
this.setState({ item: "", array: arr, check: true })
}
console.log(this.state.array)
}
render() {
return ( < div > < input type = "text"
value = { this.state.item }
onChange = { this.setItem }
/ > < button onClick = { this.add } > Add < /button > {
this.state.check ? < div > {
this.state.array.map(item => < Item name = { item }
/>) } < /div >: null
} < /div > );
}
}
export default App
I actually wrote this code for building a item buying remainder.The problem is first item added successfully but after that i can'nt add more item.Every time i tried it overwrite the previously added item.
In your add function, if there is no item in the state, your are declaring arr to be a new (empty) array, and only pushing one item to it. Then, you use setState to overrwrite the current array with your new one (Which only contains one item)
To add to the array, you would need to first copy all of the items currently in it, then push onto them
add = () => {
let item = this.state.item;
if (item != '') {
this.setState(prevState => {
let arr = [...prevState.array]; // Shallow copy of the array currently stored in the state
arr.push(item);
return { item: '', array: arr, check: true };
});
}
console.log(this.state.array);
};
You are overwriting your array every time you add a new item.
Try this inside the add function:
before
let arr = []
after
let arr = this.state.array
This code is for a simple todo app built in react. Today I was trying to refactor my option 1 code into something like option 2 and was surprised to see that it broke my removeTask functionality. During troubleshooting I also tried option 3, which had the same results. I'm struggling to figure out why; to me option 2 and 3 look pretty much the same as option 1, just cleaner. When implementing option 2 or 3 I get no errors, yet clicking removeTask now deletes all the previous tasks. What is the difference between these three?
The problem code:
//Option 1 (working)
removeTask = (event, index) => {
event.stopPropagation();
const removedTaskArray = [...this.state.tasksarray];
removedTaskArray.splice(index, 1);
this.setState({ tasksarray: removedTaskArray });
};
//Option 2 (broken)
removeTask = (event, index) => {
event.stopPropagation();
const removedTaskArray = [...this.state.tasksarray].splice(index, 1);
this.setState({ tasksarray: removedTaskArray });
};
//Option 3 (broken)
removeTask = (event, index) => {
event.stopPropagation();
const copyOfTasksArray = [...this.state.tasksarray]
const removedTaskArray = copyOfTasksArray.splice(index, 1);
this.setState({ tasksarray: removedTaskArray });
};
Full (working) code:
import React, { Component } from 'react';
import './App.css';
/* InputTaskForm renders a form, and returns the input to our storeTask method. */
const InputTaskForm = ({ formValidation }) => {
return (
<form name="charlie" onSubmit={formValidation}>
<input name="userinput" type="text" placeholder="Task..." />
<button type="submit">Submit</button>
</form>
);
}
const DisplayTasks = ({ tasks, removeTask, strikeTask }) => {
return (
<div id="orderedList">
<ol>
{tasks.map((task, index) => (
<li onClick={() => strikeTask(index)} key={index} >
{task.strike ? <strike>{task.title}</strike> : task.title}
<button id="removeButton" onClick={event => removeTask(event, index)} >Remove</button>
</li>
))}
</ol>
</div>
);
};
class App extends Component {
state = {
userinput: '',
tasksarray: [],
}
/* ============================================== #FUNCTIONS ==============================================
=========================================================================================================== */
formValidation = event => { // event prop passed from InputTaskForm component
event.preventDefault(); // prevent form from auto-refreshing on submit
const userInput = event.target.userinput.value // userInput stored
const userInputIsBlank = userInput.trim().length < 1 // trim (remove) prefixed and affixed spaces, then check length
userInputIsBlank
? alert(`Error: invalid submission`)
: this.storeTask(userInput);
};
storeTask = userInput => { // userInput passed from formValidation function
this.setState({
userinput: userInput,
tasksarray: [...this.state.tasksarray, { title: userInput, strike: false } ] //create a copy of tasks array then add a new object into the array filled out with user input
});
document.forms["charlie"].reset();
};
//Option 1 (working)
removeTask = (event, index) => {
event.stopPropagation();
const removedTaskArray = [...this.state.tasksarray];
removedTaskArray.splice(index, 1);
this.setState({ tasksarray: removedTaskArray });
};
strikeTask = index => { // index prop passed from DisplayTasks component
const { tasksarray } = this.state
const selected = tasksarray[index];
this.setState({
tasksarray: [ // change tasksarray state to: [prior slice, change, after slice]
...tasksarray.slice(0, index), // slice off (copies) of array elements prior to index element
Object.assign(selected, {strike: !selected.strike}), // invert the selected line's strike value
...tasksarray.slice(index + 1) // slice off (copies) of array elements after index element
]
});
};
componentDidUpdate() {
console.log(this.state.tasksarray); // debugging :)
};
/* =============================================== #RENDER ================================================
=========================================================================================================== */
render() {
const { tasksarray } = this.state
const { formValidation, storeTask, removeTask, strikeTask } = this
return (
<div>
<InputTaskForm
task={storeTask}
formValidation={formValidation} />
<DisplayTasks
tasks={tasksarray}
removeTask={removeTask}
strikeTask={strikeTask} />
</div>
);
};
};
/* ================================================ #EXPORT ===============================================
=========================================================================================================== */
export default App;
Return value of splice is
An array containing the deleted elements. If only one element is
removed, an array of one element is returned. If no elements are
removed, an empty array is returned.
So in your 2nd and 3rd method you're placing deleted element arrays
so you can simply change your code to
removeTask = (event, index) => {
event.stopPropagation();
const removedTaskArray = [...this.state.tasksarray]
removedTaskArray.splice(index, 1);
this.setState({ tasksarray: removedTaskArray });
};
It all boils down to understanding what does Array##splice it actually returns an array containing the deleted items. Here is a small example:
const a = [1, 2, 3, 4, 5].splice(3, 1); // same as a = [4]
const b = [1, 2, 3, 4, 5];
const c = b.splice(3, 1); // same as b = [1, 2, 3, 5] and c = [4]
I am trying to add a FontAwesome arrow next to each item in my menu that has children (i.e. I want to indicate that you can click the element to display more data within that category). The menu is populated with json data from an API, and because it is so many nested objects, I decided to use recursion to make it work. But now I am having trouble adding an arrow only to the elements that have more data within it, instead of every single element in the menu.
Does anyone have an idea of how I could change it so the arrow only shows up next to the elements that need it? See below for image
class Menu extends React.Component {
state = {
devices: [],
objectKey: null,
tempKey: []
};
This is where I'm currently adding the arrow...
createMenuLevels = level => {
const { objectKey } = this.state;
const levelKeys = Object.entries(level).map(([key, value]) => {
return (
<ul key={key}>
<div onClick={() => this.handleDisplayNextLevel(key)}>{key} <FontAwesome name="angle-right"/> </div>
{objectKey[key] && this.createMenuLevels(value)}
</ul>
);
});
return <div>{levelKeys}</div>;
};
handleDisplayNextLevel = key => {
this.setState(prevState => ({
objectKey: {
...prevState.objectKey,
[key]: !this.state.objectKey[key]
}
}));
};
initializeTK = level => {
Object.entries(level).map(([key, value]) => {
const newTemp = this.state.tempKey;
newTemp.push(key);
this.setState({ tempKey: newTemp });
this.initializeTK(value);
});
};
initializeOK = () => {
const { tempKey } = this.state;
let tempObject = {};
tempKey.forEach(tempKey => {
tempObject[tempKey] = true;
});
this.setState({ objectKey: tempObject });
};
componentDidMount() {
axios.get("https://www.ifixit.com/api/2.0/categories").then(response => {
this.setState({ devices: response.data });
});
const { devices } = this.state;
this.initializeTK(devices);
this.initializeOK();
this.setState({ devices });
}
render() {
const { devices } = this.state;
return <div>{this.createMenuLevels(devices)}</div>;
}
}
This is what it looks like as of right now, but I would like it so items like Necktie and Umbrella don't have arrows, since there is no more data within those items to be shown
You could check in the map loop from createMenuLevels if the value is empty or not and construct the div based on that information.
createMenuLevels = level => {
const { objectKey } = this.state;
const levelKeys = Object.entries(level).map(([key, value]) => {
//check here if childs are included:
var arrow = value ? "<FontAwesome name='angle-right'/>" : "";
return (
<ul key={key}>
<div onClick={() => this.handleDisplayNextLevel(key)}>{key} {arrow} </div>
{objectKey[key] && this.createMenuLevels(value)}
</ul>
);
});
return <div>{levelKeys}</div>;
};
Instead of just checking if the value is set you could check if it is an array with: Array.isArray(value)
I'm revisiting an old problem that I asked about and approaching it differently. Currently I want to update the score array individual. Currently what happens is when the onClick function runs it removes all whole array. How do I update just that array's index that I am trying to specify???
class App extends React.Component {
constructor(props) {
super(props);
this.scoreFive = this.scoreFive.bind(this);
this.state = {
score: [10, 20]
}
}
scoreFive(key) {
this.setState((prevState) => {
return {
score: [
prevState.score[key] + 5
]
}
})
console.log(key)
}
render() {
return (
<div>
<h1>Dominoes</h1>
<Player key={1} name="micah" score={this.state.score[0]} scoreFive={() => this.scoreFive(0)} />
<Player key={2} name="kyndra" score={this.state.score[1]} scoreFive={() => this.scoreFive(1)} />
</div>
);
}
}
const newArray = this.state.score.map(element => element + 5);
And then do:
this.setState({score: newArray});
The map function returns a new array using your condition.
Any problem let me know :)
You have to take the array from the previous state, clone it, modify the certain index, and then update the state with that:
score: prevState.score.map((value, index) => index === key ? value + 5 : value)
If you do that quite often it is quite repetetive, you could also abstract that into a helper:
const lens = (key, cb) => obj => ({ ...obj, [key]: cb(obj[key]) });
const index = (index, cb) => array => array.map((v, i) => i === index ? cb(v) : v);
Usable as:
this.setState(lens("score", index(key, it => it + 5)));
Try:
scoreFive(index) {
this.setState((prevState) => {
const score = prevState.score; // reference the array
score[index] + 5; // modify the specific index
return {
score: score
};
})
console.log(key)
}
update the score and setState it..
scoreFive(key) {
let {score} = this.state;
score[key] += 5;
this.setState({score});
}
Edited----------------------
So after researching and some negative marking i found that i was doing it wrong and mutating the state as mentioned in the-power-of-not-mutating-data
So here is the updated implementation
scoreFive(key) {
this.setState({score: this.state.score.map((data, index) => index === key ? data + 5 : data) });
}
Thanks for helping me out :)
How can I update isSelected to true for the item that the user clicks on?
Here is my handleSelect function so far:
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val, index) => {
//val.isSelected = index === true;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
This function is passed down correctly via props and is working, the issue I'm having is with the logic.
The current logic sets all items in the array to true which is incorrect. I only want to update / toggle the state of the item the user clicked.
I have an array of objects within state, here is what one of the objects looks like:
state = {
allGenres: [
{
id: 1,
genreTitle: 'Horror',
isSelected: false,
}
]
}
Here's how we can do it:
On each clicked Genre, we get its id.
After we have the id, then we toggle the selected genre isSelected flag.
Please follow updateGenres method, to check how we did it in an immutable way.
updateGenres(selectedId) {
const { allGenres } = this.state
this.setState({
// Here we make sure we don't mutate the state
allGenres: allGenres.map(genre => ({
...genre,
// Toggle the clicked one, and reset all others to be `false`.
isSelected: genre.id === selectedId
// If you want to keep the values of the rest genres, then the check should be:
// isSelected: (genre.id === selectedId) ? !genre.isSelected : genre.isSelected
}))
})
}
renderGenres() {
const { allGenres } = this.state
return allGenres.map(genre => <Gengre onClick={() => this.updateGenres(genre.id) })
}
render() {
return <div>{this.renderGenres()}</div>
}
The benefit of this approach is that you don't depend on the index of the allGenres and your toggle implementation will be decoupled to the presentation. Imagine if you change the genres sort (from ASC to DESC), then you have to change your toggle logic too.
Easiest way is that maintain allGenres and selectedGenre separately in state
So handleSelect will be like
handleSelect(id) {
this.setState({
selectedGenre: id
});
}
If the i passed to handleSelect is the index of the genre in the genres array then
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val, index) => {
val.isSelected = index === i;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
if the i refers to the id of the genre then
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val) => {
val.isSelected = val.id === i;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
I'll assume that you have some components being rendered, and that you want to click in these components... You could use bind and pass to the callback function the index of the array... for example:
class Component extends React.Component {
constructor() {
super();
this.state = {
allGenres: [{
id: 1,
genreTitle: 'Horror',
isSelected: false
}]
}
}
handleSelect(i) {
//Get Genres
let genres = this.state.allGenres;
genres = genres.map((val, index) => {
val.isSelected = index === i;
return val;
});
//Set State
this.setState({
allGenres: genres
})
}
render() {
const {allGenres} = this.state;
return (
<ul>
:D
{allGenres.map((genre, i) => (
<li onClick={this.handleSelect.bind(this, i)}>
{genre.genreTitle} {genre.isSelected && 'Is Selected'}
</li>
))}
</ul>
);
}
}
I'm using bind in the onClick callback from the li, this way I have access to the genre index in your callback.
There are some other ways of doing this though, since you have the id of the genre, you might want to use that instead of the index, but it's up to you to decided.