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
Related
I'm studying React JS making a simple todo list and part of my code is as follows:
changeStatus(e, index) {
this.state.tasks[index].status = e.target.value;
this.setState((prevState) => ({
tasks: prevState.tasks
}));
}
As React documentation says, we shouldn't change state manually, we should call setState instead, so, I'm receiving a warning on console saying that, even calling setState after the manual changing.
I'm doing so because, for me, is more practical doing that than separating the task, doing a splice on my array, and calling setState with the new values.
Is there a problem with my approach? How would you recommend updating state in a efficient and non-repetitive way?
Try this :
changeStatus(e, index) {
const tmp = { ...this.state };
tmp.tasks[index].status = e.target.value;
this.setState(tmp);
}
You should not mutate the state directly, to do that create a new object from the state and then update that and set it back to state like
changeStatus(e, index) {
var tasks = {...this.state.tasks}
tasks[index].status = e.target.value;
this.setState({tasks});
}
In case you are wondering as to what {...this.state.tasks} do then check this answer:
What is the meaning of this syntax "{...x}" in Reactjs
However you can also make a copy of the state using the ES5 Object.assign() operator like
var tasks = Object.assign({}, this.state.tasks)
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 ={ ... }.
Background
I have a react redux application making use of immutable js.
Problem
For the most part this is working perfectly but some of the reducers in the application are adding several extra fields to my redux store.
Example
The fields that I can see are as follows
_root
__altered
size
This only happens some of the time. When I use a reducer that also merges the current state.
case ActionType.SUCCESS_GET_DATA : {
let newState = { ...state, [action.meta]: action.payload };
return state.merge(newState);
}
where: action.meta is the unique key/name of the data and action.payload is the data that is successfully retrieved.
Question
So I can see that creating a new state with the spread operator is causing these extra fields to be added to my state. So is there a way to use the spread operator without adding these extra fields?
Immutable maps will always add there own 'meta'
So I have come to the conclusion that Immutable maps will always add there own meta to the state. This is because its a map merged with an object.
To get around this use the method toJS()
case ActionType.SUCCESS_GET_DATA : {
let jsState = state.toJS();
let newState = { ...jsState, [action.meta]: action.payload };
return state.merge(newState);
}
Now you're merging a object with an object.
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.
I face difficulties to write reducers for Redux. Specifically I end up wrapping a lot of my state manipulation in .map, .slice etc. When the structure of my state object grows larger, the reducers become hard to read, with functions wrapping functions etc.
I didn't want to use Immutable.js as I wanted to stick to simple POJO as long as possible
I tried using combineReducers and reduceReducers libs, with are awesome. But often I end up requiring a large chunk of the state in my reducer.
I wrote a little utility, to help with creating a new state:
var path = require('immutable-path').path;
let state = {
level1: {
level21: {
level3: 3
},
level22: [{
id: 1,
val: 1
}, {
id: 2,
val: 2
}]
}
};
let newState = path(state, 'level1.level22[id=1].val', x => x + 10);
It gives a new state, leaving the original state the same. The references of objects and arrays not modified stay the same (=== comparison is true).
Can someone tells me if I'm missing an easier way with es6 syntax to perform the same results? Or another better community maintained lib to get the same benefits?
Thanks for your time and expertise.
I suggest breaking your reducer up into smaller functions.
Below are four functions, each in turn deals with a smaller part of the state. (Though in each case the first parameter is called state, it's different every time). The action though, is always the same.
I haven't checked this code for correctness, but I hope you get the idea.
function reducerTop(state, action) {
switch (action.type) {
case SET_VAL:
return Object.assign({}, state, {
[action.topLevel]: reducerNext(state[action.topLevel], action);
});
}
return state;
}
function reducerNext(state, action) {
switch (action.type) {
case SET_VAL:
return Object.assign({}, state, {
[action.nextLevel]: reducerArray(state[action.nextLevel], action);
});
}
return state;
}
function reducerArray(state, action) {
switch (action.type) {
case SET_VAL:
const index = state.findIndex((item) => item.id === action.id);
if (index > -1) {
return [
...state.slice(0, index),
reducerFinal(state[index], action),
...state.slice(index + 1)
];
}
}
return state;
}
function reducerFinal(state, action) {
switch (action.type) {
case SET_VAL:
return Object.assign({}, state, {
value: action.value
});
}
return state;
}
I think it is a problem of redux itself -- it is just too low-level, and some things (like nested updates, for instance), just should be easier. Also, I believe that there is no problems to put all business logic into action creators, and inside reducers just update our state (and to perform nested updates, if needed).
Another unnecessary thing, from my point of view -- constants. We usually have some object with all redux entities (for instance, reducers), and I think it should be enough to figure out all needed constants.
With this thoughts, I wrote a library redux-tiles, which does exactly that -- based on given type, it creates constants, handles async functions, and creates reducers for you. So, typical API request will look like the following:
import { createTile } from 'redux-tiles';
const requestTile = createTile({
type: ['api', 'request'],
fn: ({ api, params }) => api.get('/items', params),
});
It will create all constants, a reducer, and also handle failure, success and loading states. You can take a look at the full HN API example with redux-tiles here. You might find there nesting, which will update everything automatically, you just need to return an array of values to nest.