Using Redux with state set as:
{
"a": {
"a2": {
"a3": 1
}
},
"b": {
"b2": {
"b3": 1
}
}
}
When I make an update in reducer only to b2, e.g. I add b3_1 as a sibling of b3, do I need to make a copy of the whole state?
{
"a": {
"a2": {
"a3": 1
}
},
"b": {
"b2": {
"b3": 1
}
}
}
Or only b2 and its children?
{
"b2": {
"b3": 1
}
}
And then make modifications finally ending up with:
{
"a": {
"a2": {
"a3": 1
}
},
"b": {
"b2": {
"b3": 1,
"b3_1":2
}
}
}
Would love to get some clarification on this as at Redux docs it says:
It's important to remember that whenever you update a nested value,
you must also return new copies of anything above it in your state
tree. If you have state.a.b.c.d, and you want to make an update to d,
you would also need to return new copies of c, b, a, and state. This
state tree mutation diagram demonstrates how a change deep in a tree
requires changes all the way up.
But then I stumbled upon this tweet:
https://twitter.com/dan_abramov/status/688087202312491008?lang=en
Which makes me think that b2 should be shallow cloned (so b3 would be same reference) and b3_1 should be added only. My best guess is that this is correct way to do it but I would appreciate any clarification on this.
b2 should be shallow cloned (so b3 would be same reference) and b3_1 should be added only.
This.
You must also remember that the original b should also remain unchanged so it must also be cloned as part of the update.
Using the object spread operator makes this very easy:
const initialState = {
a: {
a2: {
a3: 1
}
},
b: {
b2: {
b3: 1
}
}
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case ADD_B3_1:
return { ...state, b: { ...state.b, b2: { ...state.b.b2, b3_1: action.payload } } }
default:
return state
}
}
NOTE: It is generally advised to not have deeply nested structures in a single reducer. From the Redux docs:
... each layer of nesting makes this harder to read, and gives more chances to make mistakes. This is one of several reasons why you are encouraged to keep your state flattened, and compose reducers as much as possible.
Only b3_1 should be added only as well suggested from #Michael Peyper.
You should avoid using nested structures and instead you should consider using a Normalizing State Shape, where your data is flat and reference by ids.
To simplify your job with working with state and slicing only what you need you could consider dot-prop-immutable or immutable-js.
Related
I am using VuexFire in an app and as in https://github.com/posva/vuexfire in my components I have lines like
computed: Vuex.mapGetters(['todos']),
created() {
this.$store.dispatch('setTodosRef', todosRef)
},
This works fine except when I also have some computed methods in which case I don't know what to do. I have tried things like:
computed: Vuex.mapGetters(['todos']),
computed() {
amethod: { return 1 }
},
created() {
this.$store.dispatch('setTodosRef', todosRef)
},
and
computed() {
amethod: { return 1 }
},
created() {
...Vuex.mapGetters(['todos'])
this.$store.dispatch('setTodosRef', todosRef)
},
but these, and the other things that I have tried, are clearly misguided because they don't work (i.e. the data from firebase is not available to the method.
What's the correct approach?
First of all, you will always specify your computed properties as an object, not as a method (as you're doing with computed()).
Secondly, you need to use the spread operator in the computed object:
computed: {
amethod() { return 1 },
...Vuex.mapGetters(['todos'])
}
This effectively spreads out all of the computed property methods returned by Vuex.mapGetters and defines them as properties of the computed object.
If I am interacting with an API that returns null for some objects that may or may not have value, how can I reconcile that with the reducers?
example: app state
{
foo: {
foo1: null,
foo2: {
bar: null,
bar2: null
}
}
}
but the server, when things are null, returns this:
{
foo: null
}
but it can return the full state when things have value:
{
foo: {
foo1: "somefoo,
foo2: {
bar: "barvalue,
bar2: 27
}
}
}
the problem I ham having is that my reducers are trying to load the state from the return value from the server, and then my components are trying to read from a null object and it is failing.
EDIT: the reducer and the component would look like this... so the component is trying to read some nested json, which may come back as unnreadable because the parent object is null. In this case I know I could hack up a solution that checks if the object is null and inserts my predefined initial state...
BUT...my actual example is a bigger json object than this and I know it will change in the future, so I need a solution that is not so fragile and cumbersome as adding a ton of logic here to check to make sure that every object down the nested like is not null.
var updateSettings = (settings = jsonShape, action) => {
swtich (action.type) {
case UPDATE_SETTINGS:
return Object.assign({}), settings, {
foo2: {
...settings.foo2,
bar: action.newBar
}
}
}
}
const Component = ({ settings }) => {
return (
<div>{ settings.foo2.bar }</div>
)
}
Let's say I have this state:
state: {
field1: value1,
field2: {a: 5, b: 7}
}
If a reducer wants to update only field1, can the reducer return a new object containing a new field1 and the existing object state.field2 as field2 property of the new returned state? Or does the reducer have to clone field2?
Use spread operator
return {
...state,
field1: newVal
}
Here is the link detailed immutable update patterns
http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html
Yes. Not only is recycling state permissible, it's recommended.
Say your initial state is:
{
object1: { /* key-pair values */ },
object2: { /* key-pair values */ },
}
If you update your state like this:
// bad
return {
object1: action.object1,
object2: Object.assign({}, state.object2),
}
Then your app thinks object2 has changed even when it hasn't. This may cause unnecessary calculations and re-renders in your React components.
It's much better to only update the parts of your state that have actually changed.
// good
return Object.assign({}, state, {
object1: action.object1,
});
Object.assign() is what you are looking for, now you can also use the spread operator (...) , but be aware that those are ES6 features, so something like internet explorer (even 11) will crash at both , so you will need a polyfill, check this link it will give you more infos about it and also a polyfill for older browsers
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/assign
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { b: 3 , c:2 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 3 , c: 2 }
hope it helps
I have a form that should update my store for every change done in the store. To not have to make 20-30 different actions for each property/field, i though creating one single action containing property-name and value would be a good approach. So the action looks like this:
export function(property, value){
return{
type: UPDATE_MODEL,
property: property,
value: value
}
}
When passing that into my reducer, i tried using the bracket notation.
case UPDATE_MODEL: {
return Object.assign({}, state, {[action.property]: action.value});
}
When doing this:
dispatch(myAction("Type", {Id: 1}));
My reducer will set the Type property to an array instead of just setting the property Type to the incoming object. Is there a way to rig this properly?
I tried to use _cloneDeep from lodash on the current state object i wanted to change, then used the bracket notation on that, and then returning object.assign with that new object, that actually works, it just feels very wrong though.
How about this?
function updateModel(state, action) {
return Object.assign({}, state, {
[action.property]: action.value
})
}
function updateModelAction(property, value) {
return {
type: 'UPDATE_MODEL',
property,
value
}
}
function reducer(state, action) {
switch (action.type) {
case 'UPDATE_MODEL':
return updateModel(state, action)
default:
return state
}
}
console.log(reducer({}, updateModelAction('foo', { bar: 'baz' })))
I guess my question could also summed up as something like
Is there an idiomatic ES6 way to have:
array.map(identity) === array ?
array.filter(i => true) === array ?
{obj..., attr: obj.attr} === obj ?
I know, it has not been implemented like that in ES6, but is there some possible syntax I'm missing or simple helper functions to have these properties true without resorting to an immutable lib?
I use Babel and new JS features, with immutable js objects.
I would like to know how to make my reducers more efficient and generate less unnecessary object copies
I'm not interested in a lib (Mori/ImmutableJS) solution.
I have a reducer that manages a paginated list.
The pages attribute is actually an Array[Array[item]]
Here is my reducer:
const initialState = {
isLoading: false,
pages: [],
allStamplesLoaded: false
};
function reducer(state = initialState, event) {
switch (event.name) {
case Names.STAMPLE_DELETED:
return {
...state,
pages: removeStampleFromPages(state.pages,event.data.stampleId)
};
case Names.STAMPLE_UPDATED:
return {
...state,
pages: updateStampleInPages(state.pages,event.data.apiStample)
};
case Names.STAMPLE_PAGES_CLEANED:
return {
...initialState,
};
case Names.STAMPLE_PAGE_REQUESTED:
return {
...state,
isLoading: true
};
case Names.STAMPLE_PAGE_LOADED:
const {stamplePage,isLastPage} = event.data;
return {
...state,
isLoading: false,
pages: [...state.pages, stamplePage],
isLastPage: isLastPage
};
case Names.STAMPLE_PAGE_ERROR:
return {
...state,
isLoading: false
};
default:
return state;
}
}
I also have these helper functions:
function removeStampleFromPages(pages,deletedStampleId) {
return pages.map(page => {
return page.filter(apiStample => apiStample != deletedStampleId)
})
}
function updateStampleInPages(pages,newApiStample) {
return pages.map(page => {
return updateStampleInPage(page,newApiStample);
})
}
function updateStampleInPage(page,newApiStample) {
return page.map(apiStample => {
if (apiStample.id === newApiStample.id) {
return newApiStample;
}
else {
return apiStample;
}
})
}
As you can notice, everytime an event such as STAMPLE_UPDATED is fired, then my reducer always return a new state, with a new array of array of pages, even if none of the items of the array were actually updated. This creates unnecessary object copying and GC.
I don't wan to optimize this prematurely nor introduce an immutable library in my app, but I'd like to know if there are any idiomatic ES6 ways to solve this problem?
Immutable data structures such as Immutable.js and Mori use a clever trick to avoid recreating the whole structure all the time.
The strategy is fairly simple: when you update a property drill down to the property, change it and rewrap all the property from this node till the root.
Let's assume you want to change the property c to 4 in the following state:
const state1 = {
a: {
b: {
c: 1
},
d: [2, 3, 4],
e: 'Hello'
}
}
The first step is to update c to 4. After that you need to create
a new object for b (because c changed)
a new object for a (because b changed)
and new object for the state (because a changed).
Your new state will look like this (a * next to an object means the object has been recreated)
const state2 = *{
a: *{
b: *{
c: 4
},
d: [2, 3, 4],
e: 'Hello'
}
}
Notice how d and e have not been touched.
You can now verify things are properly working:
state1 === state2 // false
state1.a === state2.a // false
state1.a.b === state2.a.b //false
state1.d === state2.d // true
state1.e === state2.e // true
You may notice that d and e are shared between state1 and state2.
You could use a similar strategy to share information in your state without recreating a whole new state all the time.
As for your initial question:
array.map(identity) !== array
array.filter(i => true) !== array
{obj..., attr: obj.attr} !== obj
the answer is very simple.
When an array or an object is created, the Javascript VM assigns internally an identifier to that object. The identifier is incremental, so no two arrays/objects are alike.
When you perform an identity check on arrays or objects, only the internal identifier is checked for a match.
a = [] // internal identifier 1
[] // internal identifier to 2
b = [] // internal identifier 3
a === b // id 1 === id 3 is FALSE!
a === a // id 1 === id 1 is TRUE!