How to delete element from react list and maintain indexes? - javascript

I'm trying to delete elements from a list on react but the way I've found to implement this requires a key to be assigned to reach element. I'm using the index from the list, but when I remove an item from the middle, it is always the last the one who disappears. How can I solve this?
This is how I'm removing the element (I've already logged the result and it is ok):
handle_delete_elem(index){
this.state.tasks.splice(index,1);
this.setState({
tasks: this.state.tasks
});
}
And this is how I'm looping through the list:
get_task_list(){
let task_list = [];
if(this.state && this.state.tasks){
for (var i = 0; i < this.state.tasks.length; i++) {
task_list.push((
<TaskItem
key={i}
index={i}
handle_delete={this.handle_delete_elem}
value={this.state.tasks[i].text}>
</TaskItem>
));
}
}
return task_list;
}

I solved this problem by used spread operators.
I've refactored your code a bit:
class List extends React.Component{
constructor(){
super();
this.state = {
arr: [1,2,3,4,5]
}
this.get_task_list = this.get_task_list.bind(this);
this.handle_delete_elem = this.handle_delete_elem.bind(this);
}
handle_delete_elem(index){
let newArray = [
...this.state.arr.slice(0, index),
...this.state.arr.slice(index+1, this.state.arr.length)
];
this.setState({
arr: newArray
});
}
get_task_list(){
if(this.state && this.state.arr){
return this.state.arr.map( (arrItem, index) => (
<ListItem
key={index}
arrItem={arrItem}
id={index}
onClick={this.handle_delete_elem}
/>
));
}
}
render(){
return(
<ul>{this.get_task_list()}</ul>
)
}
}
const ListItem = (props) => (
<li onClick={() => props.onClick(props.id)}>{props.arrItem}</li>
);
Check this in the fiddle: https://jsfiddle.net/69z2wepo/83396/

Related

Passing onChange event from mapped child to parent's state returns 'undefined' error

Hello [from react beginner].
Trying to pass child's input value to parent state.
So, App has an array:
export default class App extends React.Component {
state = {
data: [
{id: 1, name: 'john'},
{id: 2, name: 'doe'},
]
}
render() {
return (
<List data={this.state.data}/>
)
}
}
Then List takes prop.data as state.data and returns children in map:
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
this.parentChange = this.parentChange.bind(this);
}
renderList() {
const data = this.state.data;
let list = null;
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) => this.parentChange(e, index)} />)
});
} else {
list = <p>nothing here</p>
}
return list;
}
parentChange(value, index) {
// pls give me anything
console.log('--- value: ', value);
console.log('--- index: ', index);
}
render() {
return (
<div>{this.renderList()}</div>
)
}
}
And Item child:
class Item extends React.Component {
render() {
const {id, name} = this.props.data;
return (
<div>
<input id={id} value={name} onChange={(e) => this.props.onChange(e, id)} />
</div>
)
}
}
But if I change any input's value there is an error as result
Cannot read property 'parentChange' of undefined
Thanks for any help (code, links etc)
You are declaring a function with function keyword:
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}
Declaring a function with the function keyword will create another context inside itself, so your this (context) will no longer be the class context.
The IDE might not warn you but when it runs, JS create another context inside your function result in an undefined error.
So it will need to change to:
if (data.length) {
list = data.map((item, index) => {
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}

ReactJS remove child element created through count

I have two components, a TrackSection(the Parent element) which has a button that creates a TrackItem(child) every time it is clicked. The child elements are built through a variable numTracks which increments every time the button is clicked. The add button works fine but i'm having issues deleting a TrackItem from the array. I tried referencing the track_items directly but it won't let me.
I'm very new to React and Frontend development. Any other tips would be appreciated!
TrackSection.js
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
numTracks: 0,
};
}
onAddTrack = () => {
this.setState({
numTracks: this.state.numTracks + 1,
});
};
onDeleteTrack = () =>{
//????
};
render() {
const track_items = [];
for (var i = 0; i < this.state.numTracks; i += 1) {
track_items.push(<TrackItem key={i} id={i} onDeleteTrack = {this.onDeleteTrack(i)}/>);
}
return (
<div>
<Button onClick={this.onAddTrack}>
+new track
</Button>
{track_items}
</div>
);
}
}
TrackItem.js
class TrackItem extends Component{
constructor(props){
super(props);
this.state = {
id: this.props.id,
name: '',
}
}
render(){
var onDeleteTrack = this.props.onDeleteTrack
return(
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete</button>
</Grid>
);
}}
Issue
You are using an array index as the React key, and the id. When you remove an element from the array you may remove it from the array, but since the items shift up to fill the "hole" now all the elements in the array have incorrect "id"/index values.
Solution
Don't use a mapped array index as a React key.
Example solution uses an incrementing id, as before, but also stores the array in state. This allows you to consistently increment the id key and retain a static id with each element.
class TrackItem extends Component {
constructor(props) {
super(props);
this.state = {
id: this.props.id,
name: ""
};
}
render() {
var onDeleteTrack = this.props.onDeleteTrack;
return (
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete {this.props.id}</button>
</Grid>
);
}
}
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: [],
id: 0,
};
}
onAddTrack = () => {
this.setState(prevState => ({
tracks: [...prevState.tracks, prevState.id],
id: prevState.id + 1,
}));
};
onDeleteTrack = (id) =>{
this.setState(prevState => ({
tracks: prevState.tracks.filter(el => el !== id)
}))
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>
+new track
</button>
{this.state.tracks.map(track => (
<TrackItem key={track} id={track} onDeleteTrack = {this.onDeleteTrack}/>
))}
</div>
);
}
}
Be careful about doing too much logic in your render function, as your current solution would recreate all the TrackItem's every time you add a new item. So React can't do optimization magic.
Second remark, now you are just having a counter, so removing a element in the middle would probably not have the effect you are looking for. I assume the track items will be having some data to them. Like name, etc. So just store those values in the state and render each item.
Here is a sample solution, modify for your needs:
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: []
};
}
onAddTrack = () => {
// Probably not the best way to create a id
const randomId = Math.random().toString();
const newTrack = {
id: randomId,
name: "Some name" + randomId
};
const newTracks = [
// the tracks we allready have added
...this.state.tracks,
// add a new track to the end
newTrack
];
// Replace state
this.setState({
tracks: newTracks
});
};
onDeleteTrack = (id) => {
// Keeps all tracks that don't match 'id'
const tracksWithOutDeleted = this.state.tracks.filter(
(track) => track.id !== id
);
// Replace the tracks, so now its gone!
this.setState({
tracks: tracksWithOutDeleted
});
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>+new track</button>
{
// Loop over tracks we have in state and render them
this.state.tracks.map((track) => {
return (
<TrackItem
id={track.id}
name={track.name}
onDeleteTrack={this.onDeleteTrack}
/>
);
})
}
</div>
);
}
}
And the TrackItem.js:
class TrackItem extends Component {
render() {
const { onDeleteTrack, id, name } = this.props;
return (
<>
<button onClick={() => onDeleteTrack(id)}>Delete {name}</button>
</>
);
}
}

React: Render children only if condition is true

I'm new in React. I'm developing a screen but I have a issue, I don't know how insert the children in the parent if the state condition is equals, I'm using an array to print the parent and children but depends of the data the parent could have a children or not, for example if (parent.rework_name === children.rework_name) ? print the children : 'nothing in the parent'.
Please let me know if you have an idea how to solve this, many many thanks in advance.
This is the goal, my code works but the damn children is outside the parent :(
class Filling extends Component {
constructor() {
super();
this.state = {
fillingStations: [],
};
}
componentDidMount() {
getDataAPI('http://localhost:8080/api/parent')
.then((station) => {
getDataAPI('http://localhost:8080/api/children')
.then((data) => {
const stationArray = [];
station.map((item, index) => {
stationArray.push(
<ReworkStation key={index} title={index + 1} status='' />,
);
data.map((it, idx) => {
const f2Date = it.f2_time.substr(0, 10);
const f2Hour = it.f2_time.substr(11, 8);
const f2DateFormatted = `${f2Date.substr(8, 2)}/${f2Date.substr(5, 2)}/${f2Date.substr(0, 4)}`;
const color = selection_color(it.color_d);
return (
stationArray.push(item.rework_name === it.rework_name && <ReworkTitle key={idx} vin={it.vin} date={f2DateFormatted} ipsq={it.defects} hour={f2Hour} color={color} />)
);
});
});
console.log(stationArray);
this.setState({
fillingStations: stationArray,
});
});
});
}
render() {
return (
<div className='row'>
{ this.state.fillingStations }
</div>
);
}
}
I don't know how to insert the children inside the parent already render.
I already solved, first render all the parent divs and after replace the position array with array.splice
render() {
const array = [];
this.state.fillingStations.map((item, index) => (
array.push(<Parent key={index} title={index + 1} status='' />),
this.state.fillingChildren.map((it, ind) => {
if (item.name === it.name) {
parent.splice(index, 1,
<Parent {...this.props}}>
<Child {...this.props} />
</Parent >);
}
})
));
return (
<div className='row'>
{array}
</div>
);
}
}

Removing item from an array of items does not work

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

How can I use `setState` with objects nested in an array in React JS?

With this code, I am able to successfully use setState on a simple object – when I click on "Joey" the name changes to "Igor".
class Card extends React.Component {
myFunc = () => {this.props.change('Igor')};
render() {
return (
<p onClick={this.myFunc}>{this.props.name}</p>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { name: "Joey" }
}
toggle = (newname) => {
this.setState((prevState, props) => ({
name: newname
}));
}
render() {
return (
<Card change={this.toggle} name={this.state.name} />
);
}
}
But with this code, which has multiple objects nested in an array, setState is either not able to change each name to "Igor" or it must be modified in some way.
class Card extends React.Component {
myFunc = () => {this.props.change('Igor')};
render() {
return (
<p onClick={this.myFunc}>{this.props.name}</p>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
names: [
{
name: "Joey"
},
{
name: "Sally"
},
{
name: "Billy"
},
]
}
}
toggle = (newname) => {
this.setState((prevState, props) => ({
// what can I put here to change the name I click on to "Igor"
}));
}
render() {
const names = this.state.names.map((name, index) => (
<Card key={index} change={this.toggle} {...name} />
))
return (
<div>
{names}
</div>
);
}
}
Even though I know this is not how setState works, I tried to access name by passing index and then writing this.state.names[index].name: newname. No surprises here, it didn't work.
I have researched and cannot find similar questions on SO about this although I have found a lot of mentions with regards to immutability helpers. But I am still not sure if that is the way to go.
What is the best way to use setState to modify objects nested in an array?
Have modified your code and the working example can be found here.
The changes can be found here:
toggle = (index, newname) => {
this.setState((prevState, props) => ({
// Return new array, do not mutate previous state.
names: [
...prevState.names.slice(0, index),
{ name: newname },
...prevState.names.slice(index + 1),
],
}));
}
render() {
const names = this.state.names.map((name, index) => (
// Need to bind the index so callback knows which item needs to be changed.
<Card key={index} change={this.toggle.bind(this, index)} {...name} />
))
return (
<div>
{names}
</div>
);
}
The idea is that you need to pass the index into the callback function via .bind, and return a new state array with the modified name. You need to pass the index so that the component knows which object to change the name to newname.
I would use this for the toggle method:
toggle = (nameYouWantChanged, nameYouWantItChangedTo) => {
this.setState({
names: this.state.names.map(obj =>
obj.name === nameYouWantChanged
? { name: nameYouWantItChangedTo }
: obj
)
})
}

Categories