I'm trying to work out the ideal way to update several top level fields on my state tree while still maintaining split reducers.
Here's a simple solution that I've come up with.
var state = {
fileOrder: [0],
files: {
0:{
id: 0,
name: 'asdf'
}
}
};
function handleAddFile(state, action) {
return {...state, ...{[action.id]:{id: action.id, name: action.name}}};
};
function addFileOrder(state, action) {
return [...state, action.id];
}
// Adding a file should create a new file, and add its id to the fileOrder array.
function addFile(state, action) {
let id = Math.max.apply(this, Object.keys(state.files)) + 1;
return {
...state,
fileOrder: addFileOrder(state.fileOrder, {id}),
files: handleAddFile(state.files, {id, name: action.name})
};
}
Currently I'm able to dispatch a single action {type: ADD_FILE, fileName: 'x'}
, then addFile
creates an action internally to send to addFileOrder
and addFile
.
I'm curious if it is considered a better approach to do either of the below.
Instead dispatch two actions, one to add a file, then get it's id and dispatch an ADD_TO_FILE_ORDER
action with the id.
OR Fire and action like {type: ADD_FILE, name: 'x', id: 1}
, instead of allowing addFile
to calculate the new id. This would allow me to use combineReducers
and filter on action type.
This example is probably trivial, but my actual state tree is a bit more complicated, with each file being added also needing to be added to other entities.
For some additional context, a more complete state tree would look like this.
{
"fileOrder": [0]
"entities": {
"files": {
0: {
id: 0,
name: 'hand.png'
}
},
"animations": {
0: {
id: 0,
name: "Base",
frames: [0]
}
},
"frames": {
0: {
id: 0,
duration: 500,
fileFrames: [0]
}
},
"fileFrames": {
0: {
id: 0,
file: 0,
top: 0,
left: 0,
visible: true
}
}
}
}
Adding a file would need to:
The last two points make me wonder if I'd be able to use combineReducers at all.
Updating State Using Reducers: The Spread Operator State can't be directly changed, to create or update state, we can use the JavaScript spread operator to make sure we don't change the value of the state directly but instead to return a new object that contains a state passed to it and the payload of the user.
The reducer must always produce the same state given the same current state and action, it must be a pure function. A common pattern is to compose multiple reducers that act on separate parts of the state, i.e. properties of the state object.
There are two main ways to initialize state for your application. The createStore method can accept an optional preloadedState value as its second argument. Reducers can also specify an initial value by looking for an incoming state argument that is undefined , and returning the value they'd like to use as a default.
I ended up finding a pretty simple solution to this problem.
Both of these blocks from the documentation are functionally the same thing.
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
});
// This is functionally equivalent.
function reducer(state, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
};
}
I ended up tweaking the second block, and always passing along my global state tree. As long as nothing edits the state along the way, all the reducers work fine.
// Simple change to pass the entire state to each reducer.
// You have to be extra careful to keep state immutable here.
function reducer(state, action) {
return {
a: doSomethingWithA(state.a, action, state),
b: processB(state.b, action, state),
c: c(state.c, action, state)
};
}
Building on the author's solution:
I've been having this same issue, where I need (just a little) access outside of my reducer's part of of the state. I think this solution can work in practice if you're diligent about not changing anything other than a single value like a flag, or a counter.
It's impurity could get crazy fast if other developers weren't as reserved with their code. Imagine what would happen if a started changing b and c's part of the state, b changing a and c's part, and so on.
You might consider shrinking the surface area of impurity like this:
function reducer(state, action) {
return {
a: doSomethingWithA(state.a, action, state.globals),
b: processB(state.b, action, state.globals),
c: c(state.c, action, state.globals)
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With