I feel like my reducer should be working, but it keeps insisting that I'm mutating the state.
Uncaught Error: A state mutation was detected inside a dispatch, in the path: output.outputList.0.composition. Take a look at the reducer(s) handling the action {"type":"SET_OUTPUT_COMPOSITION",
I posted something similar a couple hours ago with no answers, but I figured my redux state was too complicated. This is my simplified version and I'm still getting mutate errors.. what am I doing wrong? should I not be using a class in my redux state? should i be using some sort of immutable library? please help me.
My Initial Redux State
output: {
outputList: [], //composed of Output class objects
position: 0
}
Output Class
class Output {
constructor(output) {
this.id = output.id;
this.composition = output.getComposition();
this.outputObj = output;
this.name = output.name;
this.url = output.getUrl();
}
}
export default Output;
Reducer for updating property
case types.SET_OUTPUT_COMPOSITION: {
let outputListCopy = Object.assign([], [...state.outputList]);
outputListCopy[state.position].composition = action.composition;
return Object.assign({}, state, {outputList: outputListCopy});
Action
export function setOutputComposition(comp) {
return { type: types.SET_OUTPUT_COMPOSITION, composition: comp}
}
The spread operator does not deep copy the objects in your original list:
let outputListCopy = Object.assign([], [...state.outputList]);
It is a shallow copy, therefore
outputListCopy[state.position].composition = action.composition;
You are actually mutating previous state objects, as you said in your comment there are several ways to work around this, using slice/splice to create new instance of the array, etc.
You can also take a look at using ImmutableJS, in general I would say storing classes in the redux store makes the thing a bit hard to understand, I tend to favor simple structures that can be easily inspected with redux-tools.
The error is coming from dispatch. So it not even getting as far as the reducer. I expect it does not like you using class to define output. Instead just do const output ={ ... }.
Related
So I'm fairly new to React-Redux and I'm facing this problem where if i update this array with a new object in redux, i'm getting the following error
Uncaught Invariant Violation: A state mutation was detected between dispatches, in the path `settingsObject.arrayForSettings.0.updatedObject`. This may cause incorrect behavior.
The Problem only arises when i update the state in redux with new values, On previous values there is no error. The piece of code that is giving me the error is as follows.
let tempArrayForSettings = [...props.arrayForSettings];
tempArrayForSettings.forEach((element:any) => {
if(element.settingsType === "DesiredSettingType")
{
//modify elements of a JSON object and add it to the element
element.updatedObject = JSONUpdatedObject;
}
//call the action method to update the redux state
updateAction(tempArrayForSettings);
});
The error is pointing me to the following link : Redux Error
I know I'm not updating the object I'm getting from props instead I'm making a copy using the spread operator so I really don't know why the error is coming whenever I'm firing the updateAction function.
Well, your line element.updatedObject = JSONUpdatedObject; is modifying the object in the Redux store. element is a direct reference to your store object. You would need to do an immutable copy here - your spread above only does a shallow copy, but the items here are still the same.
Generally, you should do logic like this not in your component, but within your Reducer. (see the Style Guide on this topic) That also gives you the benefit that you don't need to care about immutability, as within createSlice reducers you can simply modify data.
You're updating the object inside the array, so the spread operator that you've created above only clones the array itself, not the objects inside the array. In Javascript, objects are passed by reference (read more here)
To fix this warning, you'd need to copy the object itself, and to do that, you'd need to find the specific object in the array that you'd like to change.
Try this:
const { arrayForSettings } = props;
const modifiedSettings = arrayForSettings.map((element:any) => {
if(element.settingsType === "DesiredSettingType")
{
//modify elements of a JSON object and add it to the element
return {
...element,
updatedObject: JSONUpdatedObject,
}
}
return element;
}
updateAction(modifiedSettings);
});
Also, it's recommended that this logic lives on the reducer side, not in your component.
I have an immutable.js Map stored in Redux that is structured like:
reduxObject: {
details: {}
...
objectToChange: {
myPosts: [{
name: 'someName',
link: 'someLink',
}],
drafts: []
}
}
I am trying to append the array objectToChange.myPosts in a reducer function using
let temp = state.getIn([objectToChange, myPosts])
temp.push(action.payloadData)
return state.setIn([objectToChange, myPosts], temp)
The redux data is getting updated, however the displayed redux data isn't getting rerendered. I was expecting the state.setIn to create a new immutable object causing react native to trigger a rerender. Any suggestions or help would be greatly appreciated. Thanks ahead of time
So I found a workaround to using immutable that i'm not sure is acceptable but it works. I am now using lodash's cloneDeep function.
let temp = _.cloneDeep(state)
temp[action.payloadType][action.payloadLoc].push(action.payloadData)
return (temp)
Ok so cloneDeep was a bad workaround but i found the proper solution to the problem. I needed to return the statement like this:
case 'postNewItemFbData':
let tempUpdate = state[action.payloadType][action.payloadLoc]
tempUpdate.push(action.payloadData)
return {
...state,
[`${action.payloadType}.${action.payloadLoc}`]: tempUpdate
}
When I am trying to update a property of an Object through dispatch, I can't see the update directly...
state.disable = action.payload;
return state;
the above code made me use a trick to update the state but that one has performance issue.
let tempState = Object.assign({},state);
tempState.disable = action.payload;
return tempState;
this one works fine and DOM direclty update after that. BTW app became so slow specially when I use it a lot and the state is a big object.
How can I solve this issue and force react update the dom even with a change in a property of an object?
While effective, using Object.assign() can quickly make simple reducers difficult to read given its rather verbose syntax.
An alternative approach is to use the object spread syntax recently added to the JavaScript specification. It lets you use the spread (...) operator to copy enumerable properties from one object to another in a more succinct way. The object spread operator is conceptually similar to the ES6 array spread operator.
So this could work out.
return {
...state,
disable: action.payload
}
You can use Immer
With Immer, you can write something like
import produce from 'immer';
const initialState = {
text: ''
}
const textReducer = (state = initialState, action) => {
switch(action.type) {
case UPDATE_TEXT: {
return produce(state, draft => {
draft.text = action.payload;
});
}
}
}
By the way, you should NEVER do something like
state.something = somethingelse
That breaks immutable pattern, you can read more here
Im encountering an issue with ngrx. I have an array in my state to which i want to append objects. This actually works fine if i console.log them i can see that values in my store. But the redux dev tools and the console throw the error "TypeError: Cannot freeze array buffer views with elements".
I have a state that looks like this:
const state: State = {
array: []
};
The Object i pass to my actions looks similar to this:
const obj = {attr: number, data: ImageData};
Where ImageData comes from a Canvas and is extracted with canvas.getContext("2d").getImageData(...);. It should be noted that this Object is huge with over 69000 keys with values from 0 to 255(RGBA).
And i append the new Object to the state/store like this:
createReducer(
initialState,
on(action, (state: State, action)=> {
return {
...state,
array: [...state.array, action.payload]
}
})
);
Furthermore i read that i should deepCopy Objects before passing them to the action so i did that with lodashs copyDeep(), but i got the same results.
Any help is appreciated.
You need state.array in your reducer.
return {
...state,
array: [...state.array, action.payload]
}
I had the same problem, I solved it by cloning the data that I pass to the dispatch method
ex:
const dataCloned = CloneDataInDeep.clone(dataToAdd);
this.store$.dispatch(actionToDispatch({ dataCloned}));
Another option that I have tried is to change this attribute value "strictActionImmutability" in the ngrx configuration from true to false
runtimeChecks: {
strictActionImmutability: false,
....
}
I use Angular and came across that error.
For me, deep copying the array before the dispatch worked
const copyArray = JSON.parse(JSON.stringify(array));
this.store.dispatch(actionDispatch.requestSth({ myArray: copyArray }));
Not the most beautiful solution, I know.
Edit:
Unfortunately the solution was not helpful because the objects inside the array lose the functions when strigified... So back to square one
You can use this code. This will copy the entire object so the dependency will be removed.
const newlyCreatedArray = Object.assign({}, array);
For more details: Object.assign()
We're using Redux.js with React.js (React-Redux), and in the reducer we're using the following code (we are using redux-actions to reduce boilerplate):
const update = require('react/lib/update');
const myReducer = handleActions
({
[myAction]: (state, action) => {
state = update(state, {
something: {$set: !state.someProperty}
});
return someFunction(state);
}
})
We are using update from React Immutability Helpers but assigning the result into the state using state =.
Is this against the basic Redux guidlines - i.e. mutating the state? It seems so, but the code seems to work perfectly and quickly, and the redux devtools shows the state changes correctly...
Your example is okay, because you're not actually mutating the contents of the object that the state variable points to. Instead, you're just updating the local variable named state to point to a different object instead.
It's important to understand how variables and references work in Javascript, which will help clear up what's going on here.
Personally, I'd suggest doing const newState = update(....) for clarity.