09 Apr 2018
Redux - .dispatch
Today’s post will look at what happens on store.dispatch(action)
calls after the dispatch
call invoked in createStore
(discussed in Part I of this Redux mini-series). As a refresher, the code below was generated in Part I. I’ll I’ve done is add a call to dispatch to increment my command line counter by 1.
The counter now increments to the input number plus 1. So what happened here? dispatch
is actually a very short method (26 lines total, including white space), so this blog post will attempt to explain each line. The whole of dispatch is below (recall I’m using Redux 3.7.2
):
I first want to review the commentary above the dispatch
method in the Redux source. Portions of the commentary deal with actions that are Promise
s, which Redux out of the box does not support. I’ll skip those sections for now. The first relevant section is below:
Dispatches an action. It is the only way to trigger a state change.
The
reducer
function, used to create the store, will be called with the current state tree and the givenaction
. Its return value will be considered the next state of the tree, and the change listeners will be notified.
So, invoking dispatch
is the only way to trigger a change in the store’s currentState
. dispatch
is invoked with an action
as an argument, which is passed to the reducer originally passed to createStore
. The return value from the reducer is the new currentState
of the tree. The final part of the sentence about change listeners is not yet relevant to the toy application I’m building, so I’ll ignore it for now.
As previously noted, dispatch
takes an action
as an argument, which should be a POJO (helps with redux-devtools
), needs a type property and cannot be undefined.
@param {Object} action A plain object representing “what changed”. It is a good idea to keep actions serializable so you can record and replay user sessions, or use the time travelling
redux-devtools
. An action must have atype
property which may not beundefined
. It is a good idea to use string constants for action types.
The beginning of dispatch just covers these bases and raises errors where appropriate:
There’s also a conditional that checks that isDispatching
is truthy
, and raises an error if it is.
The reason for this error is to prevent calls to dispatch
from a reducer
(hence the error “Reducers may not dispatch actions.”). Raising this error can be done by passing the store into an action and calling dispatch
from the reducer, like so:
The next part of dispatch
is a try
/ finally
block that sets isDispatching
to true (a variable declared through let
in createStore
), sets currentState
to currentReducer(currentState, action)
(currentState
is also declared via let
in createStore
), and then in the finally
sets isDispatching
back to false. Again, the only usage of isDispatching
is in this function to prevent calling dispatch
in the reducer.
Also, if you’re wondering why a try
/ finally
here, the intent is to prevent Redux from never setting isDispatching
back to false
. This would prevent the reducer from ever firing again (because of the aforementioned “Reducers may not dispatch actions” error). I actually learned this from looking at this Redux PR, and specifically this exchange:
The final part of dispatch
before the return statement is to set a listeners
array equal to currentListeners
, which is then set to nextListeners
. Both the *Listeners
variables are declared via let
in createStore
. A for
loop then iterates through the listeners and invokes each one.
In this toy example, both currentListeners
and nextListeners
are empty arrays, so there are no listeners to invoke.
The return value is just the action
passed to it:
@returns {Object} For convenience, the same action object you dispatched.
Why this is convenient, I’m not yet sure, but will trust the source for now.
We’ve done it! This post covers dispatch
without any listeners set. I’ll use the next post to generate listeners via store.subscribe
and analyze what happens in that listeners loop when listeners exist.