I am having a problem update an object of the state tree (create missing keys if they do not exist). I am using the spread operator! Here is my action:
export function addResourceOption(value) {
return function (dispatch) {
dispatch(addRsrcOpt(value))
}
};
I am taking just "value" from the action and I am using the current state values (selectedResource,resourceSubName,selectedResourceOption). here is how I am accessing the state tree:
state: {
physical_machines:{
state.selectedResource:{
state.enteredSubResourceName:{ "name": state.selectedResourceOption, "value": action.payload.value}
So I am getting the error: cannot read "enteredSubResourceName" of undefined. Here is my reducer code:
case ADD_RESOURCE_OPTION:
return {
...state,
//resourcesOptionsTable: [...state.resourcesOptionsTable, { name: action.payload.name, value: action.payload.value }],
physical_machines:{
...state.physical_machines,
[state.selectedResource]:{
...state.physical_machines.selectedResource,
[state.enteredSubResourceName]:{
//...state.physical_machines.selectedResource[state.enteredSubResourceName],
[state.selectedResourceOption] : action.payload.value
}
}
}
};
I edited my code, I can reach and place a new object in the right location, but it is overriding the previous one!
I'm not very certain but can you look and test this :
case ADD_RESOURCE_OPTION:
return {
...state,
physical_machines:{
...state.physical_machines,
state.selectedResource:{
...state.physical_machines.selectedResource,
state.enteredSubResourceName:{
...state.physical_machines.selectedResource.enteredSubResourceName,
selectedResourceOption : action.payload.value
}
}
}
};
So, I managed to solve the problem using this reducer code:
let { selectedResource, enteredSubResourceName, selectedResourceOption } = state
return Object.assign({}, state, {
physical_machines: {
...state.physical_machines,
[selectedResource + "." + enteredSubResourceName]:{
...state.physical_machines[selectedResource + "." + enteredSubResourceName],
[state.selectedResourceOption.name] : action.payload.value
}
}
});
I did some modification to the requirement as I joined the (selectedResource + "." + enteredSubResourceName), but it is still valid in my case. Now, I can add values without them overriding eachother. Thank you all for your responses.
Related
I'm calling an api request to update (edit) values in my array of objects. But the issue is i can only see the updated value after page refresh. To see the updated (edited) values instantly i'm trying to insert the updated values to my object locally (Only if the request is executed successfully). My approach is as follows
Action
.then(res => {
if (res.data.success === true) {
const updatedBillingPlan = {
"billingId":data.billingId,
"name": data.name,
"rateKWh": data.rateKWh,
"rateMin": data.rateMin,
"rateTransaction": data.rateTransaction
}
dispatch(updateBillingPlansSuccess(updatedBillingPlan));
} else {
alert("error");
}
})
Reducer
case UPDATE_BILLING_PLANS_SUCCESS:
let updatedBillingArray = (Here i need to include action.data to billingData )
return update(state, {
billingData: updatedBillingArray,
})
I'm trying to use immutability-helper library.
How would i go about this?
For example when i delete a record, to display the newest array with the deleted record , i'm doing something like this.
case DELETE_MODAL_SUCCESS:
let updatedBillingArray = state.billingData.filter(property => property.billingId !== action.data);
return {
...state,
billingData: updatedBillingArray,
billingDeleteToast: true
}
I want to do the same with update as well.
assuming billingID is unique among the billing plans, you could use that to update the proper element in the array:
case UPDATE_BILLING_PLANS_SUCCESS:
let updatedBillingArray = state.billingData.map(bp => if (bp.billingId === action.data.billingId) return action.data else return bp);
return update(state, {
...state,
billingData: updatedBillingArray,
})
What is the better way to update state in this context inside a reducer?
case DELETE_INTEREST:
let deleteInterests = state.user.interests;
let index = deleteInterests.findIndex(i => i == action.payload);
deleteInterests.splice(index, 1);
return { ...state, user: { ...state.user, interests: deleteInterests } };
ESLint doesn't like let statements inside case blocks inside a reducer, getting:
eslint: no-case-declaration - unexpected lexical declaration in case
block
ESLint doesn't like let statements inside case blocks inside a
reducer, Why?
This is discouraged because it results in the variable being in scope outside of your current case. By using a block you limit the scope of the variable to that block.
Use {} to create the block scope with case, like this:
case DELETE_INTEREST: {
let .....
return (...)
}
Check this snippet:
function withOutBraces() {
switch(1){
case 1:
let a=10;
console.log('case 1', a);
case 2:
console.log('case 2', a)
}
}
function withBraces() {
switch(1){
case 1: {
let a=10;
console.log('case 1', a);
}
case 2: {
console.log('case 2', a)
}
}
}
console.log('========First Case ============')
withOutBraces()
console.log('========Second Case ============')
withBraces();
For deleting the element from array, use array.filter, because splice will do the changes in original array. Write it like this:
case DELETE_INTEREST:
let deleteInterests = state.user.interests;
let newData = deleteInterests.filter(i => i !== action.payload);
return { ...state, user: { ...state.user, interests: newData } };
try to encapsulate the inside the case with {}
like this look simple example
case EnumCartAction.DELETE_ITEM: {
const filterItems = state.cart.filter((item) => item._id !== action.payload)
return {
...state,
cart: filterItems
}
}
An easy fix is to use brackets {} to encapsulate your case code.
If you're using return you might need to add break in some cases.
On submitting a form with some updated values, I need to update the state to reflect these changes, but I am new to Immutable.js and am unsure how to do so.
Is it possible to pass a function as a 2nd argument to set or update to update values based on certain criteria.
I have a function which receives state and an array of objects called values. The data in values looks like this:
[
{
key: 'name',
value: 'fred'
},
{
key: 'height',
value: '201'
},
{
key: 'weight',
value: '78'
}
]
I need to map over this data, and the state list, and update the corresponding values in the state list with the values array.
How can I do this. I have put together a function which the Reducer calls to update the state with the new data, but unsure exactly how to get the end result
function updateValue(state, values = []) {
const items = state.get('items').map((i) => {
const key = i.get('key');
values.map(v => {
if (v.key === key) {
return state.update('value', v.value);
}
})
});
return state.update('items', /* Can I use a function here to replace the code above.. to update all of the items in the state List that correspond to the items in the measurements array (which have new values) */);
}
Thank you very much.
Update
Tried the following, but getting the error: Expected [K, V] tuple: i
function updateValue(state, values = []) {
const items = state.get('items').map((i) => {
const key = i.get('key');
values.map(v => {
if (v.key === key) {
return state.update('value', v.value);
}
})
});
return state.update('items', items);
}
More details on the error from Immutable:
function validateEntry(entry) {
if (entry !== Object(entry)) {
throw new TypeError('Expected [K, V] tuple: ' + entry);
}
}
You can use 'merge' to return new object:
return state.merge({
items: values,
});
getInitialState: function() {
p:{'Keeper' : [] , 'Defenders': [] , 'Midfield' : [], 'Forwards' : []}}
}
onUpdatePlayers : function (newState) {
var pos;
if (newState.position.includes('Back')) {
pos = 'Defenders'
} else if (newState.position.includes('Midfield')){
pos = 'Midfield'
} else if (newState.position.includes('Forward')) {
pos = 'Forwards'
} else {
pos = newState.position;
}
this.state.p[pos].push(newState)
}
Basically , I want to push some arrays into multiple state's property.
Somehow, I need to change this code "this.state.p[pos].push(newState)" to using this.setState. I've google it and found something like
this.setState({
p : this.state.p[pos].concat([newState])
});
Obviously, It does not help at all. Can you please advise me on this ?
It will be Big Thanks,
Cheers!!
If you do really need your state to be deeply nested you'll need to replace entire p property with new object. For example using Object.assign
this.setState({
p: Object.assign(this.state.p, {
[pos]: this.state.p[pos].concat(newState)
})
})
Danny Kim, you are missing quotes on the new key you wanted to add. Change your last line to
this.state.p['pos'].push(newState)
Having a hard time figuring out what I'm doing wrong here.. and I'm sure it's something simple I'm missing. Just trying to increment counters on state whenever the data passed into this switch call matches each category, but for some reason the counter doesn't increment...
countCategories(cart) {
cart.map(function(item){
switch (item.type) {
case "beverage": return () => { this.setState({
countBeverage: this.state.countBeverage + 1
}); }
case "fruit": return () => { this.setState({
countFruit: this.state.countFruit + 1
}); }
case "vegetable": return () => { this.setState({
countVegetable: this.state.countVegetable + 1
}); }
case "snack": return () => { this.setState({
countSnack: this.state.countSnack + 1
}); }
default: return console.log("unknown category");
};
}); }
I also tried it this way, but I don't think I have a reference to 'this' when I call it this way:
countCategories(cart) {
cart.map(function(item){
switch (item.type) {
case "beverage": return this.setState({
countBeverage: this.state.countBeverage + 1
})
case "fruit": return this.setState({
countFruit: this.state.countFruit + 1
})
case "vegetable": return this.setState({
countVegetable: this.state.countVegetable + 1
})
case "snack": return this.setState({
countSnack: this.state.countSnack + 1
});
default: return console.log("unknown category");
};
}); }
Thank you so much for your help!
Just do this dude:
let food = [{type: "snack"},{type: "snack"},{type: "snack"},{type: "snack"},
{type: "veggi"},{type: "veggi"},{type: "veggi"},{type: "veggi"}]
let foodCount = {
veggiCount: this.state.veggiCount || 0,
snackCount: this.state.snackCount || 0,
beefCount: this.state.beefCount || 0,
fruitCount: this.state.fruitCount || 0
};
food.map(item => foodCount[item + "Count"]++ )
this.setState(foodCount)
The important thing here, is to setState 1. once, 2. when the calculation has completed. Avoid setting the state in loops or iterations like for(...) setState()
Assuming you're invoking countCategories bound to the component (the value of this is the component), in your first code, which should work, you could change the mapping function to an arrow function, so it keeps the this value of the countCategories function. Another weird thing I've noticed is that you're creating an array of functions by returning functions that should change the state, instead of actually changing the state:
countCategories(cart) {
// Notice the change in the next line
cart.map(item => {
switch (item.type) {
case "beverage":
// Set the state instead of returning a function that sets the state
this.setState({
countBeverage: this.state.countBeverage + 1
});
break;
case "fruit":
this.setState({
countFruit: this.state.countFruit + 1
});
break;
case "vegetable":
this.setState({
countVegetable: this.state.countVegetable + 1
});
break;
case "snack":
this.setState({
countSnack: this.state.countSnack + 1
});
break;
default:
console.log("unknown category");
break;
};
});
}
An important consideration here is that setState is asynchronous so you can't read the value in the same execution cycle and increment it. Instead, I'd suggest creating a set of changes and then apply them in a single setState.
Below I've used map to iterate over the cart and and increment the state values stored in a cloned copy of state (because this.state should be considered immutable). Then once complete, the state is updated.
let newState = Object.assign({}, this.state);
cart.map((item) => {
// adapt type to state string -> snack to countSnack
const type = item.type.replace(/^(.)/, (s) => 'count' + s.toUpperCase());
newState[type] = (newState[type] || 0) + 1;
});
this.setState(newState);
See notes within Component API documentation for details