When using Redux, you may have come across a scenario where you’d like one action creator to dispatch multiple actions.
There are a few reasons you’d want to do this, but let’s consider the problem of handling a submit
event from a form. In response, you’ll want to do multiple things. For example: RESET
your form’s view model, POST
your form’s data to the server, and also NAVIGATE
to another route.
So should you dispatch separate actions for each of these behaviours, or instead dispatch a single action which is handled by each of the applicable reducers?
Actually, this is a trick question; with the appropriate store middleware, it is possible to do both at once!
How does this work? Well, no matter what you pass to dispatch
, it is still a single action. Even if your action is an array of objects, or a function which can then create more action objects!
Dispatching functions which dispatch actions
But wait a moment. If we were to take the example of an action creator which returns a function for the redux-thunk middleware, aren’t we definitely calling dispatch
multiple times?
function submit() {
return function(dispatch) {
dispatch({
type: 'NAVIGATION/NAVIGATE',
location: {name: 'documentEdit', {id: data.id}},
)
dispatch({
type: 'DOCUMENT_VIEW/RESET',
id: data.id,
})
dispatch({
type: 'DOCUMENT_DATA/POST',
data,
})
}
}
Indeed we are – but that doesn’t change the fact that what you originally dispatched is just a single function. All that is happening here is that redux-thunk then dispatches more actions as it processes your function.
Dispatching arrays
Things are a little more obvious if you use the redux-multi middleware to handle arrays of action objects:
function submit() {
return [
{
type: 'NAVIGATION/NAVIGATE',
location: {name: 'documentEdit', {id: data.id}},
},
{
type: 'DOCUMENT_VIEW/RESET',
id: data.id,
},
{
type: 'DOCUMENT_DATA/POST',
data,
},
]
}
In this case, you’re clearly only dispatching one array object, even if the action objects it contains are then individually dispatched by the appropriate middleware.
Gotchas
The one thing to be careful of is that one call to store.dispatch
may now result in many calls to your store’s subscribers:
Sometimes this is what you actually want. But more likely, you only care about the last state from a given dispatch
. In that case, batch up your listeners using the redux-batched-subscribe store enhancer:
import { createStore, applyMiddleware } from 'redux'
import reduxMulti from 'redux-multi'
import { batchedSubscribe } from 'redux-batched-subscribe'
// Add middleware to allow our action creators to return functions and arrays
const createStoreWithMiddleware = applyMiddleware(
reduxThunk,
reduxMulti,
)(createStore)
// Ensure our listeners are only called once, even when one of the above
// middleware call the underlying store's `dispatch` multiple times
const createStoreWithBatching = batchedSubscribe(
fn => fn()
)(createStoreWithMiddleware)
const reducer = function(state = 0, action) {
return state + 1
}
// Create a store with our application reducer
const store = createStoreWithBatching(reducer)
store.subscribe(function() {
console.log(store.getState())
})
store.dispatch([
{ type: 'FOO' },
{ type: 'BAR' },
])
// Output:
// 3
So it is OK to dispatch multiple actions from an action creator?
Yes! There is absolutely nothing wrong with doing so. Don’t worry, you’re fine.
But what if I want to dispatch actions in listeners?
Now you’re getting into let’s have a good think about this territory. Dispatching actions in listeners can cause fun problems like infinite loops and terrible performance.
But these problems aside, dispatching actions in listeners can still certainly be useful. For example, you might want to fetch data based on the route you arrive at, not the route that you think you will. So wouldn’t it be great if there was a safe way to do so?
And wouldn’t you believe it, there actually is! My solution is a pattern called actors, and there is a guide to it coming really soon. Make sure to join my newsletter, or you might miss out!
And to sweeten the deal, in return for your e-mail you’ll immediately receive three print-optimised PDF cheatsheets – for React (see preview), ES6 and JavaScript promises!
I will send you useful articles, cheatsheets and code.
One more thing – I love hearing your opinions, questions, and offers of money. If you have something to say, send @james_k_nelson a tweet, or send me an e-mail at james@jamesknelson.com. Looking forward to hearing from you!
Read More
Related Projects
- See multiple dispatch in action in Unicorn Standard’s Redux/React Boilerplate
Hello. I have been dispatching actions from other actions in an application and so far no problems. But after reading your article I am thinking that maybe I am doing an anti-pattern.
This is more or less what I do in an action.
———————–
export function(payload)
{
return function(dispatch)
{
return addRecordPromise.then(actionUpdateListRecords())
}
}
————————-
What do you think?
Hi Ivan,
Don’t worry – there is nothing anti-patternish about this. Dispatching actions from action creators is ok – after all, that is why they’re called action creators! It is dispatching actions after actions have been processed which is frowned upon. For example, in a Redux `subscribe` function.
Hi James,
I’ve just read your latest articles, and thanks for sharing all this stuff !
About the topic of this post and the fact of dispatching multiples actions ending up to only one render (if I get it right), how would you handle the case of fetching some date and providing info about the loading status ?
let’s say just displaying a spinner when the fetching starts and hiding it when it’s done…
If you render just once, it doesn’t work.
I’m using the promise middleware of redux which allows us to dispatch 3 actions when fetching data : REQUEST, SUCCESS, FAILURE.
I modified these middleware so that it always returns a promise even if the action creator was not a promise.
It allows me to do things like that in my component :
buyVideoHandler (assetId, priceId) {
const { dispatch, video } = this.props
const payload = {
priceId,
productId : video._id,
assetId
}
dispatch(buyVideo(payload)).then(() => {
return dispatch(closeModal())
}).then(() => {
return dispatch(cleanCheckout())
}).then(() => {
this.context.history.pushState(null, `/${this.props.locale}/${ Routes.PATH.JOURNEY_VIEW }`)
}).catch((err) => {
debug(‘Error while buying video’, err)
})
}
Here buyVideo make a call to an API endpoint, closeModal and cleanCheckout are just synchronous actions but which still return promises so that I can chain them.
I prefer chaining the actions here (instead of inside the first action) because perhaps I don’t want to chain all these actions every time the buyVideo action will be called but only in this specific component.
What do you think about that ?
Thanks.
Boris.
Hi Boris,
I think I could probably make this a little clear in the article, but what the batching does is ensures that all of the actions dispatched from a single `dispatch` call in one tick result in only a single call to the handler function passed to
subscribe
.So in your example, most of the dispatched items will result in separate renders. Since
closeModal
andcleanCheckout
are synchronous, they should be batched into a single render call. I believe this is the behaviour you expect?Hi James,
sorry for this late reply.
You’re right ! The expected behavior would be just one rendering after all the actions are done.
I’ll try to use the redux-multi and redux-batched-subscribe middlewares for that !
Thank you.
Great write up!
I’m trying to use the redux-multi method of dispatching an array of action creators, and it seems to work except for one small oddity – my redux-logger middleware is reporting that the encapsulating array is itself an action, which gets dispatched and logged as “undefined” before the actual action creators themselves get dispatched. is there a way to have it ignore the encapsulating array?
This is an other alternative:
redux-batched-actions
https://github.com/tshelburne/redux-batched-actions
Dan Abramov suggest handling with this way:
https://github.com/reactjs/redux/issues/959#issuecomment-151446296
This may make it in: https://github.com/reactjs/redux/pull/1813/commits