Redux assign elements to nested object in state giving its key - reactjs

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

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

How to create reusable redux-module?

For example, my app contains the two list: colors & my favorite colors. How to create the re-usable filter-module for this two lists?
The problem is the actions in redux commiting into global scope, so filter-reducer for colors and filter-reducer for favorite colors reacting to the same actions.
I try something like high-order functions that receive the module-name and returned new function (reducer) where classic switch contain module + action.type.
But how make scoped actions or scoped selectors? Best-practise?
Maybe Redux-Toolkit can solve this problem?
High-order reducer
High-order action
High-order selector
Well described here
Name your function like createFilter and add a parameter like domain or scope
after this in switch check it e.g
export const createFilters = (domain, initState) => {
return (state = initState, { type, payload }) => {
switch (type) {
case: `${domain}/${FILTER_ACTIONS.ADD_FILTER}`:
...
and for actions create something like this
const createFilterActions = (domain: string) => {
return {
addFilter: (keys: string[]) => {
return {
type: `${domain}/${FILTER_ACTIONS.ADD_FILTER}`,
payload: keys
}
},
updateFilter: (key: string, state: any) => {
return {
type: `${domain}/${FILTER_ACTIONS.FILTER_UPDATED}`,
payload: { key, state }
}
},
}
}

Uncaught Invariant Violation: A state mutation was detected between dispatches when using arrayMove

I'm trying to build a React/Redux app and I am a beginner. I want to use the React Sortable HOC to have a rearrangeable list but I cannot get the new arrangement to stick.
I have a functional component where I get the list of items. The item structure is like this:
items [ {name, courseList}, {name, courseList}, ...].
To populate the table I make an api call and update the prop variable using MapStateToProps. Here's a bit of code:
function CoursesPage({
student,
studentCourses,
loadStudentCourses,
updateCoursesOrder,
...props
}) {
useEffect(() => {
if (studentCourses.length === 0) {
loadStudentCourses(student.id).catch((error) => {
alert("Loading student courses failed" + error);
});
}
}, []);
...
}
and this is the mapStateToProps function:
function mapStateToProps(state) {
return {
student: state.student,
studentCourses: state.studentCourses,
};
}
This bit works fine, and everything appears.
The problem is when I try to rearrange and save it in onSortEnd function:
function onSortEnd({ oldIndex, newIndex, collection }) {
const newCollections = [...studentCourses];
newCollections[collection].courseList = arrayMove(
newCollections[collection].courseList,
oldIndex,
newIndex
);
updateCoursesOrder(newCollections);
}
The newCollection gets populated and modified correctly and I am calling updateCoursesOrder with the items correctly arranged. That function is an action that calls a dispatch.
export function updateCoursesOrder(courseList) {
return function (dispatch) {
dispatch(setNewCourseOrderSuccess(courseList));
};
}
export function setNewCourseOrderSuccess(studentCourses) {
return { type: types.SET_NEW_COURSE_ORDER, studentCourses };
}
Using the debugger I can see that the code is running well up till the dispatch return from setNewCourseOrderSuccess().
This should go to the reducer, but instead throws an error: Uncaught Invariant Violation: A state mutation was detected between dispatches.
This is how the reducer looks like:
export default function courseReducer(
state = initialState.studentCourses,
action
) {
switch (action.type) {
case type.LOAD_STUDENT_COURSES_SUCCESS:
return action.studentCourses;
case type.SET_NEW_COURSE_ORDER:
return {
...state,
studentCourses: action.payload,
};
default:
return state;
}
}
How could I solve this issue?
Thank you very much!
With this:
const newCollections = [...studentCourses];
newCollections[collection].courseList =
Although newCollections is a new array, it's only a shallow copy of the studentCourses; the array items are not clones, they're references to current objects in state. So, assigning to a courseList property of one of those objects is mutating the state.
Replace the object at the index instead of mutating it:
function onSortEnd({ oldIndex, newIndex, collection }) {
updateCoursesOrder(studentCourses.map((item, i) => i !== collection ? item : ({
...item,
courseList: arrayMove(item.courseList, oldIndex, newIndex)
})));
}

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

Updating property of nested object in Redux using action

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

Resources