I've been beating my head against this for long enough that I'm not sure I'd even see an obvious problem.
The App has rows, and each row has cells of varying sizes. Adding rows works. Adding cells works. Deleting rows however, does not. If I click the delete button for row 0, row 2 disappears. If I click the button for row 1, row 2 disappears. Looking at the react dev tools, it appears that under the hood the right row is being deleted (in state), the right row is selected for deletion, but no matter what it doesn't render the way I would expect (if I hit delete for row 0, row 0 disappears, for example).
If I delete from the highest row down it works, but if I start at the zeroth row it won't let itself be deleted.
deleteRowButtonHandler of the App class is where I've been focusing my efforts.
deleteRowButtonHandler( e )
{
const targetRow = parseInt( e.target.id )
console.log( "TARGETING: ", targetRow )
const currentRows = this.state.rows
let newArray = []
currentRows.forEach( entry =>
{
console.log( "Looking at ", entry.id )
if( entry.id == targetRow )
console.log( 'skipping entry with id', entry.id )
else
newArray.push( entry )
})
console.log( newArray )
this.setState( {rows: newArray})
//let newRows = currentRows.filter( thisRow => {console.log( thisRow.id + ' and the target ' + targetRow);
// if( thisRow.id == targetRow )
// {
// console.log( thisRow )
// console.log( '...is the row that we are trying to delete' )
// }
// return thisRow.id !== targetRow}
//)
//this.setState( {rows: newArray}, console.log( "NEW STATE:", this.state ))
}
I've tried a few variations (filter, splice, doing it in a few lines, doing it in a lot of lines, etc) so I'm not particularly proud of that chunk of code at the moment.
https://codepen.io/philipvdg/pen/mdmmWLG is the code; any pointers in the right direction would be greatly appreciated.
Update
Speaking generally, the problem was that I (being a newbie) didn't understand that the constructor was only run once. In the constructor of a child component I had this.cells = this.props.cells and then, in the render method, called this.cells.map( ... ). Since the constructor only ever ran once, this.cells was never updated after a state change.
It get duplicate ids somehow.
Looking at 3
pen.js:74 Looking at 4
pen.js:81 (4) [{…}, {…}, {…}, {…}]
pen.js:225 TEMPLATE PROPS: {rows: Array(4)}rows: Array(4)0: {cells: Array(0), id: 0}1: {cells: Array(1), id: 3}2: {cells: Array(0), id: 3}3: {cells: Array(0), id: 4}length: 4__proto__: Array(0)__proto__: Object
pen.js:232 row 0 is {cells: Array(0), id: 0}
pen.js:232 row 1 is {cells: Array(1), id: 3}
pen.js:232 row 2 is {cells: Array(0), id: 3}
pen.js:232 row 3 is {cells: Array(0), id: 4}
// in newRowButtonHandler
this.setState( { rows: [ ...this.state.rows, {cells:[], id: this.state.rows.length} ] } )
// try this
this.setState( { rows: [ ...this.state.rows, {cells:[], id: Math.round(Math.random() * 1000000)} ] }) )
UPD
Problem with id's, render and delete function use different set of values. https://codepen.io/Asmyshlyaev177/pen/yLbXBGo?editors=0010
Should not use e.target.id, should be single source of truth e.g. react state.
In Controls render you should add line const id = row.id; so it will be like this:
this.props.rows.map(
( row ) => {
const id = row.id;
let rowId = 'controlRow' + id
Using array idx is wrong. You should use idx from data structure.
Related
I'm sure this isn't surprising to some out there, but I am getting something very unexpected in the following component:
const GameBoard = ({
moves = [[1,2]]
}:Props) => {
const [sideLength, setSideLength] = useState(3);
let rows = Array(sideLength).fill(Array(sideLength).fill(''));
console.log(rows);
for(let i = 0; i < moves.length; i++){
const [y,x] = moves[i];
const mark:Mark = i % 2 === 0 ? 'X' : 'O';
rows[y][x] = mark;
}
console.log(rows);
return (
<div className="game-board">
{ rows.map(row => <Row row={row}/>)};
</div>
);
};
This component represents a tic tac toe gameboard. A prop called moves should determine where markings are made on the board. In the following line, I start with a blank board:
let rows = Array(sideLength).fill(Array(sideLength).fill(''));
And then I populate the rows with markings based on moves. [[1,2]] means that only the first move has been made (X according to the rules), and it was placed one down and 2 over.
I expect the console.log after let rows... to log:
[ '', '', '']
['', '', '']
['', '', '']
Instead, both this line and the log after the for loop logs the following:
0
:
['', '', 'X']
['', '', 'X']
['', '', 'X']
So my questions are:
why isn't the first log only full of empty strings?
why is an entire column of the second log filled instead of just one square ([2,1])?
https://codesandbox.io/s/muddy-bash-shdpoi?file=/src/App.js
Re. 1: it actually is! The issue you are probably facing is that you are looking at the console in the browser. There, the content of a printed object may change after the printing to the console. If instead of console.log(rows) you use console.log(JSON.stringify(rows)) you'll see that it actually is what you expected (all empty strings).
Re. 2: This is because you are initializing all three top level array element with the same array (it's a reference!). This is even shown in the MDN documentation of the Array.fill method, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill#using_fill:
// A single object, referenced by each slot of the array:
const arr = Array(3).fill({}); // [{}, {}, {}]
arr[0].hi = "hi"; // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]
You'll need to create three different arrays to fill the top level array with.
Even replicating the object succeeded. However, the id value is duplicated the same. Therefore, if you click the delete button, the deletion will be done together.But I want to get a different ID. How can I fix it here?
const handleduplicate = id => {
const dupDataGroup = [...Groupdata].filter(item => item.id === id);
const newGroup = [dupDataGroup[0], ...Groupdata];
setGroupData(newGroup);
};
The result when I didn't do anything =>
0: {id: 0, title: 'blur', subtitle: 'Img'}
1: {id: 1, title: 'blurs', subtitle: 'Ima'}
2: {id: 2, title: 'Free ', subtitle: 'Ima'}
handleduplicate click result=>
0: {**id: 0**, title: 'blur', subtitle: 'Img'}
1: {**id: 0,** title: 'blur', subtitle: 'Img'}
2: {id: 1, title: 'blurs', subtitle: 'Ima'}
3: {id: 2, title: 'Free ', subtitle: 'Ima'}
I hope that only the ID value will change when duplicated.
React does not automatically update the key index in JSON objects. Also, in this scenario, you're fetching the value and inserting it into the newGroup.
If you really want to update the keys, I'd suggest creating a for loop to update the values of id.
const handleduplicate = id => {
const dupDataGroup = [...Groupdata].filter(item => item.id === id);
const newGroup = [dupDataGroup[0], ...Groupdata];
for (var i = 0; i < newGroup.length; i++)
newGroup[i].id = i;
setGroupData(newGroup);
};
You can duplicate an object by using the spread operator.
const obj = {name: 'ritik', surname: 'banger'};
const objCopy = {...obj};
or use of Object.assign will work for you.
let objcopy = Object.assign({}, obj);
You must note that here we are referencing the original object 'obj' and thus any changes in 'objcopy' will reflect in 'obj', this is known as shallow copy.
If you want to deep copy the object and you wish to have different reference for both then,
Using JQuery library:
import jQ from "jquery";
let objcopy = jQ.extend(true, obj, {});
Using lodash library:
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
[...Groupdata] creates a new list with the (non-duplicated) items in Groupdata. This is important if you'd want to add the item twice to the list, as changing one with change the same object as you already noticed. You will also have to duplicate the item itself.
Note that duplicating the list itself isn't necessary if you're just going to filter on it.
What I also think is happening, given the additional information in the comments, is that setGroupData is used multiple times. If you use Groupdata, followed by setGroupData, then Groupdata is not updated accordingly. So just make sure you use that function once.
const handleduplicate = (groupData, id, newId) => {
const dupDataGroup = groupData.filter(item => item.id === id); // no copying here.
const rest = groupData.filter(item => item.id !== id);
const newGroup = [
{ // the item is duplicated (note: no deep copy)
...dupDataGroup[0],
id: newId // updated with the new identifier
},
...rest
];
return newGroup;
};
const duplicated = handleduplicate(Groupdata, 123, 456);
setGroupData(duplicated);
I want to remove an element from my array when click on a specific row.
When I click on an element it does nothing or the last row gets deleted.
I tried to remove the element like this:
ondeleterow(e: any) {
const array = [...this.state.fields.columns]; // make a separate copy of the array
const index = array.indexOf(e.target.id);
if (index !== -1) {
array.splice(index, 1);
this.setState({ fields: { columns: array }});
}
}
My array/json object looks like this:
[ {index: 0, name: "person 1", age: 12},
{index: 1, name: "person 2", age: 19},
{index: 2, name: "person 3", age: 34},
]
My result should be when I click on a row with ID=1 the row with index: 1 gets deleted from my state array.
I can't give them an Id because when I submit the json structure then does not get accepted.
I feel like your Array.splice might be causing the issue here (because even though you created a new array, the objects in the array are still passed by reference).
I would recommend a completely different method of doing this operation which I've found to be far cleaner and robust.
First you have to add a unique id field to each row. (this is good practice in react anyway, instead of using index for keys).
ondeleterow(id: string) {
return (e: any) => {
const array = this.state.fields.column.filter(item => item.id != id)
this.setState({ fields: { columns: array }});
}
}
and when you're mapping over your rows, you can simply add the function to the onClick like this
<Row key={item.id} onClick={ondeleterow(item.id)} />
Never use splice in react especially with state. They directly mutate the data. Use non mutating operations like slice.
Your code should as follows
ondeleterow(e: any) {
const array = [...this.state.fields.columns]; // make a separate copy of the array
const index = array.indexOf(e.target.id);
if (index !== -1) {
array.splice(index, 1);
this.setState({ fields: {
columns: [ ...array.slice(0, index), ...array.slice(index + 1, array.length) ]
}});
}
}
You can use Array.filter. This will allow you to create a new array with only the items you want based on a certain criteria. In this case, you want an array with items that have a different ID that the one you want to remove. So it will look like this
// Actual processing
const filterByIndex = (arr, idx) => arr.filter(x => x.index !== idx);
// Your data
const json = [{
index: 0,
name: "person 1",
age: 12
},
{
index: 1,
name: "person 2",
age: 19
},
{
index: 2,
name: "person 3",
age: 34
},
];
// Printing the result
console.log(filterByIndex(json, 1));
In your React app
ondeleterow(e: any) {
const columns = this.state.fields.columns.filter(x => x.index !== e.target.id);
this.setState({ fields: { columns }});
}
Try this
onDeleteRow(e) {
const afterRemoval = this.setState.fields.columns.filter(item => item.index != e.target.id);
this.setState(prevState => ({ fields: { ...prevState.fields, columns: afterRemoval } }));
}
The other solution above sets the fields field directly, It may work but will cause problem if fields has some other attribute other than columns (those attributes will get removed)
I am going to Push some objects in an array and delete them according to there specific ID.
Now the challenge is that i wants to do the both push and delete using a single toggle button.
this.state = {
array: [{
id: 1,
name: "Abc",
checkBoxState: true
}, ]
}
handleData(label, value, id) {
let obj = JSON.stringify({
id: id,
name: label,
checkBoxState: value
});
let array = this.state.array;
array.push(obj);
console.log(array);
}
Please tell me the method how to make it possible on a single button.
For Example if i press ok button i will fetch the properties and push into an array and if i press again this button it will have to delete the object from array according to the ID.
Edit based on your comments. First check to see if the item exists in the array. If it does, delete it. If it does not add a new item. I don't have time to test but something like this may work.
this.state = {
array: [{
id: 1,
name: "Abc",
checkBoxState: true
}]
}
handleData(label, value, id) {
let array = this.state.array;
let arrayIds = Object.values
for (let item of array) {
if (item.id === id) {
let deletedObj = array.filter(item => item.id === id)
this.setState({
array: deletedObj,
})
return
}
}
let obj = JSON.stringify({
id: id,
name: label,
checkBoxState: value
});
array.push(obj);
this.setState({
array
})
}
}
console.log(this.state.array);
}
I am trying do loop over a prop and push the parent and child to a variable. I seem to be able to get the looping and pushing working correctly, but its pushing all the children under each parent
var options = [];
var option = [];
Object.keys(this.props.data.options).forEach((key, index) => {
options.push(
<AddToCartRow key={index} option={key} options={option} cartKey={this.props.cartKey} />,
Object.values(this.props.data.options[key]).forEach((value, index) => {
option.push(value)
})
)
})
Below is what is currently happening
The expected result should be
size
- 0[price: 3.95, title: Small]
- 1 [price: 4.95, title: Large]
blend
- 0[price: 0, title: Regular]
- 1 [price: 0, title: Decaf]
etc
It seems you have problem with pushing element into array. I have managed by doing as below
for (let parentData of mainObj) {
let tempArray = []; // need to declare here
for (let childData of parentData.someField) {
tempArray.push({
...
});
}
finalArray.push(tempArray);
}
You need to push your child loop data into temporary array and then to main resultant array which is options in your case .