Below code doesn't look good to me, as I have to declare a temp variable, any shorter code to achieve the same result?
handleChange = (e, index1, innerIndex) => {
const temp_values = this.state.values
temp_values.map((value, index) => {
if (index === innerIndex) {
temp_values[index].args[index1] = e.target.value
}
})
this.setState({
values: temp_values
})
}
Yes, you can simplify it like this:
handleChange = (e, index1, innerIndex) => {
this.setState({
values: this.state.values.map((value, index) => {
const args = [].concat(value.args);
if (index === innerIndex) {
args[index1] = e.target.value;
}
return {
...value,
args,
};
}),
});
}
Related
I want to ask if then is a way to update the whole object in immerjs
For example
I want to set my redux state to the output of this function
Sample state
{
"1":{
// properties
},
"2":{
// properties
}
}
In the Code below mainly, there are 3 function
ObjectPush: what it does is basically shift all properties down by one(eg 1 becomes 2)from the index(param)
ObjectRemove: Remove particular index and change indexes
deleteStrip:Reducer
const ObjectPush = (obj, index) => {
return produce(obj, (state) => {
const keys = Object.keys(state)
.filter((v) => v !== 'misc')
.map(Number);
const draft = Object.assign({}, state);
keys.forEach((key) => {
if (key >= index) {
state[key + 1] = draft[key];
}
});
delete state[index];
});
};
export const ObjectRemove = (obj, index) => {
const state = {};
const updatedState = { ...ObjectPush(obj, index)
};
const keys = Object.keys(updatedState)
.filter((v) => v !== 'misc')
.map(Number);
const temp = Object.assign({}, obj);
keys.map((key) => {
if (temp[key]) {
if (key > index) {
state[key - 1] = temp[key];
} else {
state[key] = temp[key];
}
}
});
return state;
};
// inside immer produce
deleteStrip: (state, action) => {
const { index } = action.payload;
state = ObjectRemove(state, index);
}
I have from with input type number, which has an pretty simple onChange function when entered a value the last number is missed out
Example:
If 99 is type in the input box, only 9 is recorded, and when 2523 is typed in the input box 252 is been recorded, the last number is been skipped
Here is the sandbox link
inside of onChange function
const handleRequestingCost = (e, index) => {
setCostsFormValues({
...costsFormValues,
[e.target.name]: [e.target.value]
});
for (const key in costsFormValues) {
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
...
What I am missing here? Or if is this the bad way to do ?
setState is async. So you are reading costsFormValues before it is updated
import "./styles.css";
import { Button, Form } from "react-bootstrap";
import React, { useState } from "react";
export default function App() {
const [costsFormValues, setCostsFormValues] = useState({});
const [row, setRow] = useState([
{
index: 5399,
name: "user name",
quantity: "1000",
remainingQty: 2000,
requestingCost: [],
budgetedCost: [
{
key: "Labour Cost",
value: 176
},
{
key: "Material Cost",
value: 890
}
],
amountForQuantity: [
{
key: "Labour Cost",
value: 150
},
{
key: "Material Cost",
value: 570
}
]
}
]);
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
const updatedFormValues = { // added this local variable which holds latest value update
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
};
for (const key in updatedFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: updatedFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
console.log(updatedFormValues);
};
Please try to make changes to a local variable rather than setting the state object directly as it takes about 300-500ms to update after the setState() call; as it creates queues for performance optimization.
Please refer to the code snippet:
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
let TempValue = {};
TempValue = {
...TempValue,
[e.target.name]: [e.target.value]
};
for (const key in TempValue) {
// loop each array and merge into one object
const mappedData = {
[key]: TempValue[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
console.log(TempValue);
};
As setState is async, you should use the value just after mutate it, that why we have useEffect hook to do it instead
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
for (const key in costsFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
};
useEffect(() => {
console.log(costsFormValues);
}, [costsFormValues]);
The console will show the right value, but be careful with my snippet, your handleRequestCost do some stuff with costsFormValues you should pass it to useEffect as well
const handleRequestingCost = (e, index) => {
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
};
useEffect(() => {
const mappedDataArray = [];
console.log(costsFormValues);
for (const key in costsFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
}, [costsFormValues]);
Something like this, but there is the index that I didnt get from you logic yet
I am doing a React JS Cart and I am having problems when I try to delete an Item from the there. It has already a function that adds the items and also another for the total quantity and the total price.
This is the ContextProvider:
import { useState } from "react";
import { CartContext } from "./CartContext";
export const CartProvider = ({ children }) => {
const [list, setList] = useState([]);
const addCart = (varietalCount) => {
if (list.find((item) => item.id === varietalCount.id)) {
const newVarietal = list.map((varietal) => {
if (varietal.id === varietalCount.id) {
return { ...varietal, count: varietalCount.count + varietal.count };
}
return varietal;
});
setList(newVarietal);
} else {
setList((state) => {
return [...state, varietalCount];
});
}
};
console.log("list", list);
// const deleteProd = (varietalCount) => {
// if (list.find((item) => item.id === varietalCount.id)) {
// const deleteVarietal = list.map((varietal) => {
// if (varietal.id === varietalCount.id) {
// return { ...varietal, count: null };
// }
// return varietal;
// });
// setList(deleteVarietal);
// } else {
// setList((state) => {
// return [...state, varietalCount];
// });
// }
// };
const totalPrice = () => {
return list.reduce((prev, next) => (prev + (next.count * next.price)), 0)
};
const totalQuantity = () => {
return list.reduce((prev, next) => (prev + (next.count)), 0)
};
return(
<>
<CartContext.Provider value={{ list, addCart, totalPrice, totalQuantity }}>
{children}
</CartContext.Provider>
</>);
};
If it is necessary I can add to the post the Cart.js or the ItemDetail.js. I hope someone can help me. Cheers
I think you can just use filter given that your state has value of an array. Something like:
const deleteProd = (varietalCount) => {
const newItems = list.filter((item) => item.id !== varietalCount.id)
setList(newItems);
};
You can check more array functions from here https://www.w3schools.com/jsref/jsref_obj_array.asp
I'm trying to update the state, the current func (handleLabelChange) theoretically works (updating state), but I want to add name instead of textOne and value "????" dynamically from the textarea field or any other field. Some kind of target.name and target.value, but I don't know how to deal with it.
handleLabelChange = id => {
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
return {
...item,
textOne: "????" // grab textarea name and value here
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
};
JSX inside map function:
{this.state.items.map(el => (
<textarea rows="5" placeholder="Text here..." name="textOne" onChange={() => this.handleLabelChange(el.id)}></textarea>
}
I would do something like this on the textarea:
onChange={this.handleLabelChange(el.id, 'name')}
Where the second argument is the property, and then handleLabelChange looks like this
function handleLabelChange(id, property) {
return ev => {
const newVal = ev.target.value;
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
const newItem = Object.assign({}, item);
newItem[property] = newVal;
return newItem;
} else {
return item;
}
});
this.setState({
items: updatedItems
});
}
}
Your handleLabelChange returns the callback function, rather than being the callback function
In the end I was able to solve it, if someone have a similar problem, it is my solution.
As TKoL suggested function needed args: id, name, value, ({target: {id, value, name}}), then in my case, I needed to change the id from string to number.
handleLabelChange = ({target: {id, value, name}}) => {
const idToNumber = Number(id);
const updatedItems = this.state.items.map(item => {
if (item.id === idToNumber) {
return {
...item,
[name]: value
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
}
JSX
{this.state.items.map(el => (
<textarea rows="5" placeholder="Text here..." id={el.id} name="textOne" onChange={this.handleLabelChange}></textarea>
}
I have initialized some const, lets say A, using getDerivedStateFromProps. Now I want to update the value on some action using setState but it's not working.
constructor(props) {
super(props)
this.state = {
A: []
}
static getDerivedStateFromProps(nextProps, prevState) {
const A = nextProps.A
return {
A
}
}
handleDragStart(e,data) {
e.dataTransfer.setData('item', data)
}
handleDragOver(e) {
e.preventDefault()
}
handleDrop(e, cat) {
const id = e.dataTransfer.getData('item')
const item = find(propEq('id', Number(id)), this.state.A)
const data = {
...item.data,
category: cat,
}
const val = {
...item,
data
}
this.setState({
A: item,
})
}
}
**Listing the items and Drag and Drop to Categorize**
{this.state.A.map((item, index) => (
<ListRow
key={`lm${index}`}
draggable
name={item.name ? item.name : ''}
description={item.data ? item.data.description : ''}
type={item.data ? item.data.target_types : ''}
source={item.data ? item.data.source : ''}
stars={item.data ? item.data.stars : []}
onDragStart={e => this.handleDragStart(e, item.id)}
onDragOver={e => this.handleDragOver(e)}
onDrop={e => this.handleDrop(e, 'process')}
onModal={() => this.handleToggleModal(item)}
/>
))}
I expect the value of A to be an item from HandleDrop but it's returning the same value that is loaded from getDerivedStateFromProps.
Here's how I solved this problem.
I used componentDidUpdate instead of getDerivedStatesFromProps.
componentDidUpdate(prevProps) {
if (!equals(this.props.A, prevPropsA)) {
const A = this.props.A
this.setState({
A
})
}
}
And the handleDrop function as
handleDrop(e, cat) {
const id = e.dataTransfer.getData('item')
const item = find(propEq('id', Number(id)), this.state.A)
const data = {
....data,
category: cat,
}
const val = {
...quota,
data
}
let {A} = this.state
const index = findIndex(propEq('id', Number(id)), A)
if (!equals(index, -1)) {
A = update(index, val, A)
}
this.setState({
A
})
}
Thank you very much for all of your help. Any suggestions or feedback for optimizing this sol will be highly appreciated. Thanks