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.
Related
I am trying to write my first custom hook.
I don't understand why the error is showing, as the function is declared a few lines above where it is called.
Relevant code:
function getNextAnswerState(answer: string) {
switch (answer) {
case "blank":
return ("correct");
case "correct":
return ("wrong");
case "wrong":
return ("forgotten");
case "forgotten":
return ("blank");
}
return "blank";
}
function useAnswerState() {
const [answer, setAnswer] = useState("blank");
const handleGetNextCellStatus = () => {
let nextCellStatus = getNextAnswerState(answer)
setAnswer(nextCellStatus);
}
return {answer, handleGetNextCellStatus};
}
interface letterProps {
cellIndex: number,
layerSize: number,
letter: string
}
function LetterCell({cellIndex, layerSize, letter}: letterProps) {
const {answerStatus, setAnswerToNext} = useAnswerState();
...
The errors:
[tsserver 2448] [E] Block-scoped variable 'useAnswerState' used before its declaration.
[tsserver 2454] [E] Variable 'useAnswerState' is used before being assigned.
Looks like it was a small syntax error somewhere.
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,
})
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.
While reading the documentation about updating nested state object in Redux, I stumbled upon this common mistake, which states that doing a shallow copy of the top level object is not sufficient:
function updateNestedState(state, action) {
// Problem: this only does a shallow copy!
let newState = {...state};
// ERROR: nestedState is still the same object!
newState.nestedState.nestedField = action.data;
return newState;
}
but I couldn't find out why it isn't sufficient, because technically it's working, As you can see in this fiddle:
https://codesandbox.io/s/D9l93OpDB
I'll be happy for further clarification/explanation regarding this statement, and also will be happy for an example of what is the best practice of updating such state objects.
Thanks!
Assume you have a state object like that:
let state = { a: { b: 1 } };
let cp = { ...state }
cp.a === state.a //true, that means that those are the exact same objects.
that is because the »inner« Object (state.a) is added to cp by reference. If you now do:
cp.a.b = 10;
You change the value also in state.a.b. So the mistake here is: if you want to modify state.a.b, than you have to replace that with a new Object, having a new value like so:
let cp = {
...state,
a: {
...state.a.b
b: 2
}
}
The reason for this is that you are asked to write »pure« functions, to explain consider this:
var a = { b: 1 };
//not a pure function
function foo (obj) {
obj.b++;
return obj;
}
for (var i = 0; i < 10; i++) foo(a);
//a.b = 11;
So the object is modified each call of foo(a) will produce different output and modify a global variable.
The above can lead you to really nasty bugs, which are hard to find and to prevent you from running into this prefer the below.
//a pure function
function bar (obj) {
return {
...obj,
b + 1
}
}
for (var i = 0; i < 10; i++) bar(a);
//a.b = 2
In that case a is not modified, so the output of bar(a) will always yield the same output.
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