Is there any inconvenient at all if I design my reducers to, instead of reading only the partial state, had access to the full state tree?
So instead of writing this:
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
I destructure state inside doSomethingWithA, c or processB reducers, separately:
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state, action), // calc next state based on a
b: processB(state, action), // calc next state based on b
c: c(state, action) // calc next state based on a, b and c
}
}
Would I'd be using more RAM? Is there any performance inconvenient? I understand that in javascript, a reference is always passed as parameter, that's why we should return a new object if we want to update the state or use Immutable.JS to enforce immutability, so... again, would it be of any inconvenient at all?
No, there's nothing wrong with that. Part of the reason for writing update logic as individual functions instead of separate Flux "stores" is that it gives you explicit control over chains of dependencies. If the logic for updating state.b depends on having state.a updated first, you can do that.
You may want to read through the Structuring Reducers section in the Redux docs, particularly the Beyond combineReducers topic. It discusses other various reducer structures besides the typical combineReducers approach. I also give some examples of this kind of structure in my blog post Practical Redux, Part 7: Form Change Handling, Data Editing, and Feature Reducers.
Related
In the Redux Style Guide, it is strongly recommended to Put as Much Logic as Possible in Reducers:
Wherever possible, try to put as much of the logic for calculating a
new state into the appropriate reducer, rather than in the code that
prepares and dispatches the action (like a click handler).
What I'm not sure of is, if thunks are also considered to be "the code" of some sort. Besides, we've also been (mis?)using thunks to grab data from other slices of state.
Hypothetically simplified code snippet of such thunk:
const addX = x => (dispatch, getState) => {
const { data, view } = getState();
const { y } = view; // <-- here accessing data from `view` state.
const yy = doSomeLogicWith(y);
const z = doSomeMoreLogicWith(yy);
dispatch({ type: 'data/xAdded', payload: { x, z } });
};
Is this actually considered to be an anti-pattern in Redux? If so, what are the cons of doing this?
Yes, a thunk would qualify as "the code that dispatches the action" for this case. So, what the rule is recommending here is that if possible, the action would just contain y, and the function calls to doSomeLogicWith(y) and doSomeMoreLogicWith(yy) would ideally exist within the reducer instead.
Having said that, it's totally fine for a thunk to extract pieces of data from the state and include that in the action, and it's not wrong for a thunk to do some pre-processing of data before dispatching the action.
The style guide rule is just saying that, given a choice between running a particular piece of logic in a reducer or outside a reducer, prefer to do it in the reducer if at all possible.
Given the following (and assuming we cannot change the state's structure):
StoreModule.forRoot({
a: aReducer,
b: {
b1: b1Reducer,
b2: b2Reducer
}
});
and b1Reducer is dependent on the value of a (for example because it contains something like user info).
What is the most idiomatic way to access (read-only) a in b1Reducer?
The solution I came up with is using #ngrx/effects, dispatch another action with a that can be used in the reducer:
#Effect()
augmentAction$ = this.action$
.ofType(Actions.Action1)
.withLatestFrom(this.store$)
.switchMap(([action, state]:[Action, AppState]) => {
const a = state.a;
return [new Actions.Action2(a)];
});
This works, but it becomes hard to manage if almost every action needs to be redispatched if a is used in many reducers. Is there a better way to handle this?
Looking at the real world example I see this setting up the api middleware:
export default store => next => action => {
const callAPI = action[CALL_API]
if (typeof callAPI === 'undefined') {
return next(action)
}
What exactly is happening here? I see that configureStore is importing whatever that is and passing it to applyMiddleware from redux, but what does this kind of statement mean in js?
I assume it's exporting an anonymous function that returns a function that returns a function? Just tried this:
var a = b => c => d => {
console.log('a:', a);
console.log('b:', b);
console.log('c:', c);
console.log('d:', d);
};
a(5)(6)(7);
// outputs b: 5, c: 6, and d: 7
Function Specialization
The arrow function notation simplifies currying in JavaScript.
Here it's just a way to do partial applications, and permits to bind arguments to the function at different times, by using Closures instead of Function.prototype.bind.
When you call applyMiddleware during Store creation, Redux will specialize your Middleware with the current Store it's been applied to.
Then it becomes a new specialized function, that only takes two arguments:
next => action
Where next is the next middleware that will be called on the Action. (Just like in Express, which popularized the concept, for request handling)
Timeline
The important thing here is that all these function specializations are done at different times.
store can be bound during Store creation.
next can be bound once it knows the Store it's been bound to, so also during Store creation, but could be updated later.
action is known only when you effectively dispatch an Action, which can happen any time.
The specialized middleware (the one which has been bound to the Store, and is already aware of the Next middleware function) will be reusable, and called for each new dispatched Action.
Functional Programming
These concepts (currying and partial application) come from the Functional Programming world.
Redux relies heavily on this paradigm, and the most important thing in Redux is the sidelining of Side-Effects (especially mutations).
Capturing directly the context of the function, or using a global Store via require, is a side-effect as your function will directly after its declaration be bound to this Store.
Instead Redux uses Currying to permit sort of Dependency Injection, and it results in a stateless function, that can be reused and specialized at runtime.
This way your Middleware is Loosely Coupled to the Store.
To understand this clearly you need to first know how middlewares work in redux. So first go through this
Now even after going through the documentation you are still confused, dont worry its a bit complicated, try reading it once again :).I understood this properly after 2-3 reads.
Now the one you mentioned in your question is a curried up ES6 syntax. If you try to convert this to vanilla javascript it would come to something like below
function (store) {
return function (next) {
return function (action) {
var callAPI = action[CALL_API];
if (typeof callAPI === 'undefined') {
return next(action);
}
};
};
};
So if you see its nothing but just chaining of functions.
Let's pretend I have a long-running function working on computing my new state.
Meanwhile another action comes in and changes the state while the first one did not finish and is working on stuff.
If I am imagining things correctly there is no actions queue and the state might be resolved in some unpredictable manner.
Should I be worried about this at all?
I don't mean real threads, just a concept for the lack of better wording. Actions are asynchronous and state keys are being accessed by reference.
I was concerned about the same thing so I just did some digging. It looks like two threads concurrently calling dispatch() (if it were possible) could raise an exception. But it shouldn't be possible and that error message points to a particular, different cause. The "actions queue" is in the browser's own event loop. That event loop runs async/interaction callbacks (from which we call dispatch()) one-at-a-time.
That's the responsibility of your own action creators and your own reducers, and heavily related to how you structure your actions and reducers conceptually. The Redux FAQ question on structuring "business logic" is very relevant here:Redux FAQ
Thunk action creators have access to getState, so it's very common to have a thunk check the current state and only dispatch under certain conditions, such as this example:
// An example of conditional dispatching based on state
const MAX_TODOS = 5;
function addTodosIfAllowed(todoText) {
return (dispatch, getState) => {
const state = getState();
if(state.todos.length < MAX_TODOS) {
dispatch({type : "ADD_TODO", text : todoText});
}
}
}
Your reducer can also have sanity checks as well:
function todosReducer(state, action) {
switch(action.type) {
case "ADD_TODO": {
if(state.todos.length >= state.maxTodos) {
return state;
}
return {
...state,
todos : state.todos.concat(action.newTodo)
}
}
default : return state;
}
}
Personally, I don't like to have my reducers just blindly merge in whatever data's in the action, unless it's very small (like, say, the name of the currently selected tab or something). I prefer to have a reasonable amount of logic in my action creator to set up the action, a minimal-ish amount of data included in the action itself, and a sufficiently smart reducer to do the work based on that action.
I have 2 reducers that I use and combine them. In the first reducer, I have something that gets all the initial data (which is also relevant for the second reducer).
How do I use the data in the state that I initialize/set from the first reducer to the second one?
function reducer1(state = initialState, action = '') {
switch (action.type) {
case constants.INITIAL_DATA:
returnstate.set('data', fromJS(document.data));
....
Then I combine both of those reducers and I want to access "data" from both of them (or pass the data as initialState to the second reducer).
A reducer should return a plain object and have no side effects. If you want the same state available in 2 different action.type cases, then they either need to be in the same function, or a parent function needs to pass the state to different reducer functions, like so:
function parentReducer(state = {}, action = '') {
switch (action.type) {
case CASE_ONE:
return childReducer1(state, action)
case CASE_TWO:
return childReducer2(state, action)
default:
return state
}
}
But this brings up an important design point: each (top-level) reducer represents a distinct branch of the Redux state tree, and you can probably always design your data store in a way that different parts of the tree don't need to be shared. In Redux (check out the DevTools), you have a single object, and the top-level keys of this object are the names of your top-level reducer functions.
Basically, if you perceive a need to set a different state in a reducer, so other reducers can use that, it evidence of a need to rethink the store's design.