I am working on a project where I have 4 options, but I should only be able to select one option per screen. The option pushes an object with an id and a score (based on the screen) to an array in the state. So if I make one selection and change to another, I'd like to able to remove the previous object and append the new one in an array in the state (for each screen). I have script to append and remove the object if I'm clicking on the same option, but I'm not sure what I would do to replace the object with a new one if I make a selection then immediately go to another.
The array looks as follows:
scores: [
[{id: "G1", score: "4"}, {id: "B1", score: "3"}, {id: "O1", score: "2"}, {id: "R1", score: "1"}],
[{id: "B2", score: "4"}, {id: "G2", score: "3"}, {id: "R2", score: "1"}, {id: "O2", score: "4"}]
]
Here is the code I have now:
handleScore = (id, score, idx) => {
const {
statements,
scores
} = this.state;
if (statements[0][idx].isSelected) {
this.setState({
scores: scores.map((item, index) => index === 0 ? [...item, {
'id': id,
'score': score
}] : item)
});
} else {
this.setState({
scores: scores.map((item, index) => index === 0 ? item.filter(i => i.id !== item[0].id) : item)
});
}
}
and in the render method I have:
<div
className={`statement ${(this.state.statements[0][index].isSelected) ? 'selected' : ''}`} key={item.id} onClick={e => {
e.preventDefault();
this.handleScore(item.id, match.params.score, index)
}}>
{item.statement}
</div>
Thank you in advance!
I don't know what you are trying to do, but you can use the splice() method to remove an item from an array. Check this repro on Stackblitz to see the result, and here is the code in case it does not works:
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
const App = () => {
const [data, setData] = React.useState([
{id: 0, score:10},
{id: 1, score:20},
{id: 2, score:30},
{id: 3, score:40},
]);
const displayItems = () => {
return data.map((item, index) => {
return (
<div>
<span>id: {item.id}, score: {item.score}</span>{' '}
<button onClick={() => replaceItem(item, index)}>Replace item</button>
</div>
)});
}
const replaceItem = (item, index) => {
const newItem = {id:4, score: 50};
let newData = [...data];
// -----Replace ONE ('1') item at specific index by the newItem
// newData.splice(index, 1, newItem);
// -----OR remove an item and place another at the first index
newData.splice(index, 1); // remove item from array
newData.unshift(item); // Put the item at the first index of the array
//------
setData(newData); // set your newData array as the new state
}
return (
<div>
{displayItems()}
</div>
);
};
render(<App />, document.getElementById("root"));
I placed two use case of splice so you can coment/uncomment to see the result.
Related
I am trying to create a cart layout that displays the number of quantity of each product listed in React.
The data of quantity is received from a redux state that comes like this:
[
{id: '630788107a0ce75d70be5350', quantity: 1, name: 'Nerea Sheppard', price: 176},
{id: '630788b388944200fdf6a6df', quantity: 1, name: 'Hollee Stanley', price: 423}
]
How to get the quantity each of the product?
const card = useSelector((state) => state.addons.cart);
addons.map((addon) => {
return (
<Button
onClick = {
() => {
dispatch(
REMOVE_FROM_CART({
id: addon._id,
})
);
}
} >
REMOVE ITEM
</Button>
<p>
// How to map and to get the quantity of each product from the card array
</p>
<Button onClick = {
() => {
dispatch(
ADD_TO_CART({
id: addon._id,
name: addon.name,
price: addon.price_in_euro_per_day,
})
);
}
} >
ADD ITEM
</Button>
)})
I guess the product-id is part of addon right? So I guess you want to find the product in the product-list which matches the addon._id?
There are serveral ways to do that, a simple, but a not a optimised solution would be"
// How to map and to get the quantity of each product from the card array
{card.find((item) => item.id === addon._id).quantity}
This will render the quantity directly into the html.
A second solution for quick access would be a hash-map and use the id as a key.
const cardMap = card.reduce((map, item) => {
map[item.id] = item;
return map;
}, {})
// In the html part:
<span>Quantity: {cardMap[addon._id].quantity}</span>
card?.find((x) => x.id === addon._id) ?
card?.find((x) => x.id === addon._id)
.quantity :
0
I am trying to use update state in a react function component but it is not working. I tried following a tutorial on pluralsite and apply it to my own project. Ideally this code should be finding the product based on the ID number and replacing the total with a new value.
Unfortunately I am getting an error saying that 'productData.find' is not a function and I'm not sure where the code being used for that is. Are there any suggestions on how to solve this issue?
This is what the data looks like. In this example I am saving the first element of the array.
export let data = [
{
name: "Name",
description:
"",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
...
];
function CompareCard({}) {
const index = 0;
const [productData, setProductData] = useState(data[index]);
function setTotalUpdate(id) {
const productPrevious = productData.find(function (rec) {
return rec.id === id;
});
const productUpdated = {
...productPrevious,
total: 1,
};
const productNew = productData.map(function (rec) {
return rec.id === id ? productUpdated : rec;
});
setProductData(productNew);
}
setTotalUpdate(1)
}
It's because productData is not an array so .find would not work. You want iterate over the products property in your data, so do productData.products.find(...)
When you do
const [productData, setProductData] = useState(data[index])
you don't pass an Array on your state but an Object (the first element of your data so an Object) and Object don't have find method.
Try
const [productData, setProductData] = useState([data[index]])
with [] on our useState to put your Object on array
///////////////////////////////// Edit /////////////
Ok, I try your code, and I propose you this.
import React, { useState } from "react";
const data = [
{
name: "Name",
description: "",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
];
const CompareCard = () => {
// setState with the subArray products from data[0], I use '...' (spread operator) inside a Array or an Object to make a shalow copy
const [productsData, setProductsData] = useState([...data[0].products]);
const setTotalUpdate = (id) => {
// find the product to change inside products collection, that's ok
const productPrevious = productsData.find((rec) => {
return rec.id === id;
});
// create a new product to change one property's value, you can do something like 'productPrevious.total = 1', same same
const productUpdated = {
...productPrevious,
total: 1,
};
// create a new products collection to update state
const productNew = productsData.map((rec) => {
return rec.id === id ? productUpdated : rec;
});
setProductsData([...productNew]);
};
const setTotalUpdateSecond = (id) => {
// create a newState
const newState = productsData.map((product) => {
// condition if id === productId and do something
if (id === product.id) {
product.total = 1;
}
// both case, I return the product (change or not)
return product;
});
setProductsData([...newState]);
};
return (
<>
<button onClick={() => setTotalUpdate(1)}>Test old method on product 1</button>
<button onClick={() => setTotalUpdateSecond(2)}>Test second method on product 2</button>
{productsData.map((product) => {
return (
<>
<p>Product Id : {product.id} / Product Total : {product.total}</p>
</>
);
})}
</>
);
};
export default CompareCard;
Can you copy / past this, try and say me if it's what you want, if yes, I explain you where the confusion was. If not, explain me, what's the problem here and I modificate.
const [todoList, setTodoList] = useState([{ id: 0, name: 'No active Todos' }]) // Defined in another file. Imported in its child component as allTodos and setTodoList for changing state using props.
const Todos = (props) => {
const setTodoList = (todoId) => {
if (props.allTodos.length === 1) {
props.setTodoList([{ id: 0, name: 'No active Todos' }])
}
else {
props.setTodoList(props.allTodos.filter(
todo => todo.id !== todoId
))
}
}
return (
<div id="todos_container">
{
props.allTodos.map((prev) => {
return (
<div id="item_container">
<button type='button' className='check_button' onClick={() => setTodoList(prev.id)} />
<div>{props.allTodos.name}</div>
</div>
)
})
}
</div>
}
Now, suppose if todoList has three items like:
[ {id: 1, name: "..."}, {id: 2, name: "..."}, {id: 3, name: "..."} ]
If I click on the 2nd item/element returned from the mapped array, then 2nd object will be removed from setTodoList and it would be like:
[ {id: 1, name: "..."}, {id: 3, name: "..."} ].
So, what I want is that the id will change itself as consecutive numbers like:
[ {id: 1, name: "..."}, {id: 2, name: "..."} ] instead of [ {id: 1, name: "..."}, {id: 3, name: "..."} ]
Using an index as a unique identifier while dealing with an array is not recommended approach.
You can use objects for storing data, with "id" as a unique identifier and a corresponding object as the value.
const [todoList, setToDoList] = useState({
1 : {id:1, name:''},
2 : {id:2, name:''}
})
pass the id as the argument to the setToDoList. State todoList can be easily modified using this approach since we will be accessing elements using a key, which is unique.
Probably the simplest way to do this would be to regenerate the array of objects each time you modify it. You can consider storing the source of truth as a Map of numbers to strings - i.e.
const todos = new Map();
todos.set(1, 'todo1');
todos.set(2, 'todo2');
// etc
Then in your child component just create an array to map over:
const list = useMemo(() => {
let arr;
props.todos.forEach((val, key) => {
arr.push({ id: key, name: val });
});
return arr;
}, [props.todos.values()]);
return (
// todo will have the type: { id: number; name: string; }
{list.map(todo => // JSX here)}
)
In the delete function, just call props.todos.delete(id)
suppose i am having an array of like
[
{ id: 1, name: "John" , selected: true}
{ id: 2, name: "Jim" , selected: false }
{ id: 3, name: "James" , selected: false }
]
I am having function which get id and on the basis of that id I want to change the selected property of object to true and other selected property object to false
here is my function what i have tried but its throwing error
const handleOnClick = (id) => {
const temps = state.findIndex((sub) => sub.id === id)
setState(prevState => ({
...prevState,
[prevState[temps].selected]: !prevState[temps].selected
}))
Use a .map() to iterate everything. You shouldn't change values within the array without recreating the tree down to the changed object. See below example.
Below you will:
map over every element
Spread their current values
Overwrite the selected depending on if the id is the newly "selected" id
I also supplied an additional function to update using the index of the item in the array instead of by id since your title is "how to update object using index in react state?", although your question also mentions changing by id. I would say go by id if you have the choice, array indices can change.
const App = () => {
const [state, setState] = React.useState([
{ id: 1, name: "John" , selected: true},
{ id: 2, name: "Jim" , selected: false },
{ id: 3, name: "James" , selected: false }
]);
// This takes the ID of the item
const handleOnClick = (id) => {
const newState = state.map(item => {
return {
...item,
selected: item.id === id
};
});
setState(newState);
};
// This takes the index of the item in the array
const handleOnClickByIndex = (indexToSelect) => {
const newState = state.map((item, idx) => {
return {
...item,
selected: indexToSelect === idx
};
});
setState(newState);
};
return (
<div>
{state.map((item, mapIndex) => (
<div key={item.id}>
<div>
<span>{item.name} is selected: {item.selected ? "yes" : "no"}</span>
{/* This will update by ID*/}
<button onClick={() => handleOnClick(item.id)}>Select using ID</button>
{/* This will update by the index of the array (supplied by map) */}
<button onClick={() => handleOnClickByIndex(mapIndex)}>Select using index</button>
</div>
</div>
))}
</div>
);
};
ReactDOM.render(
<App/>,
document.getElementById("app")
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="app"></div>
What I am doing is trying to remove an item from array1 and
add it to array2. see code below:
for(var i = 0; i < this.state.Array1.length; i++){
if(this.state.Array1[i].Id === item.Id){
var copiedArray1 = [...this.state.Array1];
copiedArray1.splice(i, 1);
var copiedArray2= [item, ...this.state.Array2]
this.setState({
Array1: copiedArray1,
Array2: copiedArray2
});
console.log('state', this.state)
}
}
the first time it doesn`t add the item to the array stays empty
Seconde time is add is to the array but it doesnt change the view.
Someone know how to fix this?
you are setting your state inside the for loop
also iterating the and creating yout copy arrays inside the for loop as well
this casuses creation of new arrays and setting state each time the function runs in the for loop. Move setstates and the array declerations outside of the for loop then it will work
const array1 = [1,2,3,4,5];
let array2 = [];
const id =4
const found = array1.filter(item=> item === id);
if(found && found[0]) {
array2 = [...array2, found[0]]
}
this.setState({
array2
})
es6 clean code. modify it according to your needs (Also do not setState on loop)
I made a little sample, you can run it and click on the items of the left array (Array1) and transfer it to the second one.
This is the crucial part of code doing the transfer of items and updating state:
const foundItem = this.state.Array1.find(arrayItem => arrayItem.Id === item.Id);
if (foundItem) {
const copiedArray1 = this.state.Array1.filter(fItem => fItem.Id !== item.Id);
const copiedArray2= [ foundItem, ...this.state.Array2];
this.setState({
Array1: copiedArray1,
Array2: copiedArray2
}, () => console.log('state', this.state));
}
I would use it instead of the for loop because I don't like the idea of removing items from the same array you are iterating through. Also, I would suggest using setState method outside of the for loop, it will be called asynchronously.
class Demo extends React.Component {
state = {
Array1: [ {Id: 1}, {Id: 11}, {Id: 12}, {Id: 13}, {Id: 14}, {Id: 15} ],
Array2: [ {Id: 3} ]
};
handleClick = (item) => {
const foundItem = this.state.Array1.find(arrayItem => arrayItem.Id === item.Id);
if (foundItem) {
const copiedArray1 = this.state.Array1.filter(fItem => fItem.Id !== item.Id);
const copiedArray2= [ foundItem, ...this.state.Array2];
this.setState({
Array1: copiedArray1,
Array2: copiedArray2
}, () => console.log('state', this.state));
}
}
render() {
return (
<div style={{display: 'flex' }}>
<h1>Array1</h1>
<ul style={{ marginRight: 50 }}>
{this.state.Array1.map(item => <li key={`A1-${item.Id}`} onClick={() => this.handleClick(item)}>{item.Id}</li>)}
</ul>
<h1>Array2</h1>
<ul>
{this.state.Array2.map(item => <li key={`A2-${item.Id}`} onClick={() => this.handleClick(item)}>{item.Id}</li>)}
</ul>
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('demo'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="demo"></div>