Updating property of nested object in Redux using action - reactjs

Inside my reducer, my initial state has a structure like the following:
const initialState = {
showOverlay: false,
chosenAnimal: null,
sliderValue: 0,
colorValues: {
a: null,
c: null,
g: null,
t: null,
bg: null
}
};
I'm attempting to update one of the colorValues properties based on an action.type.
return {
...state,
colorValues: {...state.colorValues, action.basePair: action.colorHex}
};
action.basePair is giving me a parsing error which makes it seem as if I cannot set the property name dynamically using action. If I change action.basePair to gfor example, then the expected behavior occurs and the value of the property g is indeed updated as expected.
But is there anyway to do this so that I can set the property name dynamically through action? Thank you.
edit: The code of my action is the following:
const mapDispatchToProps = dispatch => {
return {
onSetColorValue: (basePair, colorHex) =>
dispatch({ type: "SET_COLOR_VALUES", basePair: basePair, colorHex: colorHex })
};
};

action.basePair is giving me a parsing error which makes it seem as if
I cannot set the property name dynamically using action.
Wrap action.basePair to [action.basePair]
return {
...state,
colorValues: {...state.colorValues, [action.basePair]: action.colorHex}
};
Also, it is a good practice to use payload key for action params
dispatch({ type: "SET_COLOR_VALUES", payload: { basePair, colorHex })
Reducer
return {
...state,
colorValues: {
...state.colorValues, [action.payload.basePair]: action.payload.colorHex
}
};

when using dynamic value as key, we can use the square brackets notation. Please see below:
return {
...state,
colorValues: {
...state.colorValues,
[action.basePair]: action.colorHex
}
};
You can visit below link for more details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors

Related

Update deeply nested state object in redux without spread operator

I've been breaking my head for a week or something with this !!
My redux state looks similar to this
{
data: {
chunk_1: {
deep: {
message: "Hi"
}
},
chunk_2: {
something: {
something_else: {...}
}
},
... + more
},
meta: {
session: {...},
loading: true (or false)
}
}
I have an array of keys like ["path", "to", "node"] and some data which the last node of my deeply nested state object should be replaced with, in my action.payload.
Clearly I can't use spread operator as shown in the docs (coz, my keys array is dynamic and can change both in values and in length).
I already tried using Immutable.js but in vain.. Here's my code
// Importing modules ------
import {fromJS} from "immutable";
// Initializing State ---------
const InitialState = fromJS({ // Immutable.Map() doesn't work either
data: { ... },
meta: {
session: {
user: {},
},
loading: false,
error: "",
},
});
// Redux Root Reducer ------------
function StoreReducer(state = InitialState, action) {
switch (action.type) {
case START_LOADING:
return state.setIn(["meta"], (x) => {
return { ...x, loading: true };
});
case ADD_DATA: {
const keys = action.payload.keys; // This is a valid array of keys
return state.updateIn(keys, () => action.payload); // setIn doesn't work either
}
}
Error I get..
Uncaught TypeError: state.setIn (or state.updateIn) is not a function
at StoreReducer (reducers.js:33:1)
at k (<anonymous>:2235:16)
at D (<anonymous>:2251:13)
at <anonymous>:2464:20
at Object.dispatch (redux.js:288:1)
at e (<anonymous>:2494:20)
at serializableStateInvariantMiddleware.ts:172:1
at index.js:20:1
at Object.dispatch (immutableStateInvariantMiddleware.ts:258:1)
at Object.dispatch (<anonymous>:3665:80)
What I want ?
The correct way to update my redux state (deeply nested object) with a array containing the keys.
Please note that you are using an incredibly outdated style of Redux. We are not recommending hand-written switch..case reducers or the immutable library since 2019. Instead, you should be using the official Redux Toolkit with createSlice, which allows you to just write mutating logic in your case reducers (and thus also just using any helper library if you want to use one).
Please read Why Redux Toolkit is how to use Redux today.
you could use something like that:
import { merge, set } from 'lodash';
export default createReducer(initialState, {
...
[updateSettingsByPath]: (state, action) => {
const {
payload: { path, value },
} = action;
const newState = merge({}, state);
set(newState, path, value);
return newState; },
...}

React Redux: update local state with a new value on action dispatch

I have an action that creates a 'supplier' in the database, and when it finishes it calls another action that is supposed to create a supplier locally (through redux).
This is very basic but I am a bit lost on how to add the new supplier to the already existing supplier state.
Here is the action (The newly created supplier is passed to it):
export const createSup = (sup) => {
return {
type: "CREATE_SUP",
sup,
};
};
Here is my reducer file along with the error line (Everything working fine except for the CREATE_SUP case):
const initialState = {
value : {},
loaded: false,
error: false
}
const supReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_SUPPLIERS':
return {
value: action.sups,
loaded: false, //this might need to be set to true
error: false
}
case 'LOAD_SUPPLIERS_ERROR':
console.log("load suppliers error")
return {
...state,
loaded: false,
error: true
}
case 'LOAD_SUPPLIERS_SUCCESS':
return {
...state,
loaded: true,
error: false
}
case 'CREATE_SUP':
return {
value: {...state.value, action.sup}, //************ERROR *************//
loaded: true,
error: false
}
default:
return state;
}
};
export default supReducer;
It gives me an error mark under action.sup (I guess it expects only one value). Should I not worry about returning the old value of the state, and just return the new supplier? (I think I am missing a point or two on how exactly reducers work).
Thank you for any help.
My guess would be that you meant to use the spread operator, like so:
case 'CREATE_SUP':
return {
value: { ...state.value, ...action.sup },
loaded: true,
error: false
}
action.sup isn't an object property, it's an object. I assume you just want to apply those properties to your state object.
I should also note that your reducer should typically always return a state object in the same "shape" for every action. So if your initial state object is { prop1: 1, prop2: 2, prop3: 3 }, it should always return something similar to that, just with different values. I say this because it looks like your "LOAD_SUPPLIERS" action is kinda doing its own thing and not copying the current state. Just remember that your state object is meant to be accessed "globally" (I use this term loosely) with Redux. The components that use your Redux state will expect a certain object shape and certain properties to always be there, so you need to make sure you always return that with your reducer. Hope that makes sense.

using spread operator (...) in mapStateToProps causes stale value to be passed to child component

I have code like this for my component which is a function component:
useEffect(() => {
if (errors['update'].error || successes['update'].success) {
setUpdateInProgress(false);
}
}, [errors['update'].error, successes['update'].success])
error and success properties are independent of one another, and when either of them is true, I want to call setUpdateInProgress.
in mapStateToProps I have code like this:
const mapStateToProps = (store) => {
return {
// other fields
errors: {...store.prop1.errors, ...store.prop2.errors},
successes: {...store.prop1.errors, ...store.prop2.errors};
}
These errors and successes objects are the props I need to send to my component in order for the useEffect to work. As is apparent, I want to combine properties from multiple places into one(prop1,prop2, ...).
Problem is, it's not working, and the useEffect is not called. To get it to work, I have to split the props like below:
const mapStateToProps = (store) => {
return {
// other fields
prop1Errors: store.prop1.errors,
prop2Errors: store.prop2.errors,
prop1Successes: store.prop1.successes,
prop2Successes: store.prop2.successes
}
Why would the spread operator lead to a stale value being set from the state whereas using the state value directly wouldn't?
Edit: For those interested, errors and successes have a structure like this:
errors: {
create: {
error: true,
message: "Error in create"
},
update: {
error: false,
message: ""
}
//... same thing for other actions like fetch, etc
}
//... same thing for successes
As for the reducer code, it's something like this:
function reducer(state, action) {
if(action === "UPDATE") {
return {...state, errors: { ...state.errors, update: { message: '', error: false }}, successes: { ...state.successes, update: { message: 'Update successful.', success: true }}}
}
}
Spread properties in object initializers copy their own enumerable properties from a provided object onto the newly created object. If the spread property already exists in the newly created object, it will be automatically replaced.
In other words, if you have 2 objects: const foo = {a: 1} and const bar = {a: 2, b: 1}. The result of {...foo, ...bar} would be {a: 2, b: 1}. So you can see that the assigned a property was the one from the last spread object.
This is exactly your case. You must consider deep merging since you have a multi-dimensional structure for errors and successes.
Based on your errors structure, I would do a function that merges the errors/successes as follows:
const mergeMessages = (type, ...messages) =>
{
return messages.reduce((carry, message) => {
for (let [key, value] of Object.entries(message)) {
if (!carry.hasOwnProperty(key) || value[type] === true) {
carry[key] = value;
}
}
return carry;
}, {});
}
Then you can use it like:
const mapStateToProps = (store) => {
return {
// other fields
errors: mergeMessages('error', store.prop1.errors, store.prop2.errors),
successes: mergeMessages('success', store.prop1.successes, store.prop2.successes),
}
}

How to pass nested object within an object in reducer

I'm trying to pass a nested object within an object within a reducer.
im telling redux to pass like along with the ...post. However it's giving me
like is not defined.
Reducer
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:0,
postId:null
}
case ADD_LIKE:
console.log(action.id) // renders post id which is 2
console.log(state.posts) // logs posts array
console.log(state.posts)
return {
...state,
posts: state.posts.map(post => {
if (post.id === action.id) {
post.Likes.map( (like) => {
console.log(like); // renders like log
})
}
return {
...post,
...like, /// need to pass the like here but it says is not defined
Likes: like.Likes + 1
}
})
};
console.log(like); // renders this
Like count are being called here
<Like like={id} likes={Likes.length} />
I think you have a problem inside the return.
From the code, Here where you initialize the like variable (inside the map).
post.Likes.map( (like) => {
console.log(like); // renders like log
})
So, the scope of like will not exist outside of it.
But, you are trying to access it outside in return.
return {
...post,
...like,
Likes: like.Likes + 1
}
Edited
If I understand your expectation correct.
Here the solution.
return {
...post,
...post.Likes,
Likes: post.Likes.length + 1
}

Redux assign elements to nested object in state giving its key

giving a state tree that looks like this:
machines:{
"first_machine" : {},
"second_machine" : {options:[], packages[]},
}
How can I assign "options" and "packages" to a given "machine name" in the Reducer (all machine names exist in the state tree, I just need to access to the one that its key matches the payload.machineName and assign to it)?
This is the action creator:
export const submitconfigMachine = (machineName, options, packages) =>({
type: CONFIG_MACHINE,
payload: {machineName, options, packages}
});
const { machineName, ...data } = action.payload;
return {
...state,
machines: {
...state.machines,
[machineName]: data,
},
};

Resources