Can I update whole draft object in immerjs - javascript

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

Related

Error: An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft

My code:
removeIndexCart: (state, action) => {
const IteamIndex_dec = state.cart.findIndex(
(iteam) => iteam.id === action.payload.id
);
if (state.cart[IteamIndex_dec].qnty >= 1) {
const dltiteams = (state.cart[IteamIndex_dec].qnty -= 1);
console.log([...state.cart, dltiteams]);
return {
...state,
cart: [...state.cart],
};
} else if (state.cart[IteamIndex_dec].qnty === 1) {
const data = state.cart.filter((el) => el.id !== action.payload);
return {
...state,
cart: data,
};
}
},
I wanted to reduce the selected products with index, but I encountered this error.
It seems that the reducer function is both trying to modify the proxy state with Immer (automatically applied by redux-toolkit), and returning a new value, which caused the error.
More about using redux-toolkit reducer with immer
Perhaps consider to always modify the proxy state in the reducer (only works in redux-toolkit where Immer is used internally), without returning a new value:
// Only works in redux-toolkit with immer
removeIndexCart: (state, action) => {
const IteamIndex_dec = state.cart.findIndex(
(iteam) => iteam.id === action.payload.id
);
if (state.cart[IteamIndex_dec].qnty > 1) {
state.cart[IteamIndex_dec].qnty -= 1;
} else if (state.cart[IteamIndex_dec].qnty === 1) {
state.cart = state.cart.filter((el) => el.id !== action.payload);
}
},
Alternatively, perhaps try always return a new value, without modifying the proxy state:
removeIndexCart: (state, action) => {
const IteamIndex_dec = state.cart.findIndex(
(iteam) => iteam.id === action.payload.id
);
if (state.cart[IteamIndex_dec].qnty > 1) {
const updatedItem = {
...state.cart[IteamIndex_dec],
qnty: state.cart[IteamIndex_dec].qnty - 1,
};
const updatedCart = [...state.cart];
updatedCart[IteamIndex_dec] = updatedItem;
return {
...state,
cart: updatedCart,
};
} else if (state.cart[IteamIndex_dec].qnty === 1) {
const data = state.cart.filter((el) => el.id !== action.payload);
return {
...state,
cart: data,
};
}
},

React 17.0.1 Form onChange skips the last number

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

React Context delete item from Cart

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

Need to delete item from array using redux

so I have a reducer that is adding to array
create reducer :
export default (itemsList = [], action) => {
if (action.type === 'ADD_ITEM') {
return [...itemsList, action.payload]
}
return itemList
}
deleting reducer (99% that something is wrong here, but I have no idea what ):
export default (itemList = [], action) => {
if (action.type === 'DELETE_ITEM') {
return [...itemList, itemList.filter(item => item !== action.payload)]
}
return itemList
};
action/index.js:
export const addItemToList = item => {
return {
type: 'ADD_ITEM',
payload: selectedItem
}
};
export const deleteItemFromList = item => {
return{
type: 'DELETE_ITEM',
payload: selectedItem
}
};
let say I have
itemList = [ 'abc', 'xyz', 'qwe' ]
and I want to use deleteItem('xyz') to delete 'xyz' from itemList
While deleting you just need to return the filtered list and not use spread operator too.
export default (itemList = [], action) => {
if (action.type === 'DELETE_AUTHOR') {
return itemList.filter(item => item !== action.payload)
}
return listOfAuthorsSelected
};
Array.filter() returns a new array with given filter condition and not mutate the existing array. You don't have need to use ...itemList(spread operator). Here you are actually adding a sub array each time.
Here is a simple running example
var array1 = ["abc", "def" , "ghi"];
var array2 = [...array1, array1.filter((item) => {
return item !== "def"
})];
document.write(array2);
// correct way to filter
var array3 = ["abc", "def" , "ghi"];
var array4 = array3.filter((item) => {
return item !== "def"
});
document.write("<hr/>"+array4);

update array in nested array without mutation

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

Categories