redux-batched-actions
redux-batched-actions copied to clipboard
Supporting thunks in batches
Hi! My batch includes simple actions which return plain objects and also thunks, which, in place, also can hold some batches. Is it planned to support thunks in batches?
function actionOne(param) = {return {type: 'ACTION_ONE', payload: param}}
function actionTwo(param) = {return (dispatch, getState) => {dispatch(batchActions([actionOne(param)]))}}
dispatch(batchActions[actionOne(param), actionTwo(param)])
I would expect this to already work - if you have redux-thunk enabled in the store that uses the batching reducer, I wouldn't expect to ever see an action come through that is a function.
Does not work with thunks. When the actions are functions:
It calls the
batchingReducer
for each of them and passes function into default reducer which assumes that action is object
It's tricky to add support for thunks, because they work on different level. For the alternative we can use redux-saga.
I see. It would be nice to drop a few lines in the docs to cover that issue
I'm going to look into it as well - @oluckyman in the meantime, if you want to submit doc updates, happy to merge it.
So i see the new documentation. It works fine for batchedActions inside a thunk. However how does it work for batchActions(ACTION, THUNK, THUNK)? Any update on this?
Landed to this discussion with same question. Ended up making improved batchActions
function
import {batchActions as batch} from 'redux-batched-actions'
export function batchActions(actions) {
let resolved = []
return (dispatch, getState) => {
const resolve = payload => {
resolved = [...resolved, payload]
if (resolved.length === actions.length) dispatch(batch(resolved))
}
for (const action of actions) {
if (typeof action === 'function') action(resolve, getState)
else resolve(action)
}
}
}
~~@tshelburne if this looks reasonable to you would be happy to make a pull request either for changing batchActions
or providing another function in addition~~
UPD It has it's own quirks though, this won't play well for complex actions running other thunked actions underneath
@jonashartwig No update so far - I'm in the middle of a product release, so haven't had a moment to step back to this yet.
@yavorskiy I would rather look for a more universal answer, but this does give a bit more direction to things. Thanks for the update!
Just finished working on a more universal solution to this problem - should be compatible with (but does not require) thunks, as well as other more "complex" actions that are enabled by middleware.
I decided to create a new repository (redux-batch-enhancer) since the solution I came up with significantly modifies both how package works internally and the setup/installation API (it's now a store enhancer, and works a lot more like redux-batched-subscribe). The batchActions function can be used in the same way though!
@tshelburne Would love to hear what you think!
Great, I will take a look!
I'm a little confused by the documentation. It seems to say that using thunks is supported, but when I tried it (only adding enableBatching
, without even introducing batchActions
) I am seeing problems.
// Ordinary action (works fine)
dispatch({
type: 'TESTLOGIN'
});
// Thunk (Disappears into the void)
const testLoginThunk = () => {
return (dis) => {
dis({ type: 'TESTLOGIN' });
};
};
dispatch(testLoginThunk());
It looks like this issue is about something very similar so posting here.
@dpwrussell Yes, I suspect the way I'm handling reduce breaks in the case of thunks - I intend to fix it, but haven't had the time to step back and think it through. @abc123s submitted an alternative above - I'm generally not much a fan of stateful approaches, but I don't have a better solution right now, so I would definitely encourage you to check that out. Hopefully after my company's product launches I can jump back to this! :)
@gaearon I think there would be a lot fewer problems like this with thunks if redux-thunk
operated on actions like this:
{
type: 'THUNK',
thunk: (dispatch, getState) => {...}
}
Of course the folks in this thread could use this approach as an alternative to redux-thunk
.
It may be nice not to have to type all of that out, but I think it would save people time in the long run anyway, because they wouldn't run into issues like this.
@jedwards1211 That seems like a great idea when redux-thunk
is at the point of a BC break. Had never even considered that.
@tshelburne nor had I before yesterday, but once the idea popped into my head, I was surprised Dan ever had the idea of dispatching functions to begin with.
I think this is solving the problem from the wrong end. Stuff like this just introduces more issues later on:
for (const action of actions) {
if (typeof action === 'function') action(resolve, getState)
else resolve(action)
}
This is basically making assumption functions always mean thunks which breaks other middleware with different opinions (e.g. redux-observable
).
There is just no clean way to implement support for thunks with redux-batched-actions
. Middleware wasn't designed for this use case, and twisting it to do this makes the problem worse.
In my view redux-batched-actions
exists for a rare use case. If you have batch
calls sprinkled across your codebase and need to often dispatch more than a single action you might need to rethink how you use Redux. It is generally unnecessary to fire many actions if you can fire just one. And if you absolutely need batching, doing this automatically with something like redux-batched-subscribe might work better for you.
@gaearon that makes sense, but it sounds to me like the whole idea of dispatching functions in redux-thunk
or any other library gets really hairy. It's basically impossible to use two different middlewares that handle function actions, unless you tag the functions with some property to identify which middleware should handle them, which would be awkward.
Not sure what you mean.
@gaearon I have an app that redux-batched-subscribe
wouldn't work for. I have to batch actions for meteor collection changes with 1 second debouncing to get acceptable performance when loading thousands of documents, but I need other actions, like a user dragging plots around, to go through at 30+ fps. So I can't just debounce notification of all subscribers.
Can you put the batching as a separate layer before dispatching actions? It doesn't have to be done in the middleware, you can create any abstractions for yourself.
@gaearon what I mean is redux-thunk
thinks functions are thunks, and it sounds like you mean redux-observable
thinks functions are something else, so it's only possible to use one of the two in a store. If redux-thunk
handled {type: 'THUNK', thunk: function() {...}}
actions and redux-observable
handled object actions with a different type and wrapped functions, then both could be used in the same store.
@gaearon possibly I could put the batching layer elsewhere. I just ended up using it because I had already written reducers to handle individual collection actions before I realized it caused a performance issue with larger data sets, so batching the actions was a simple enough solution.
@gaearon I think you're right, it's probably in my best interests to do the batching outside of middleware and create a reducer than handles an action containing multiple changes to a collection.
I just ran into this now with thunks. p0rsche mentioned sagas as a possible alternative.
Is it possible to batch together actions in a saga? I tried the following but it would just error out.
yield put(batchActions([
{type: "Saga Test 1"},
{type: "Saga Test 2"}
]));
EDIT: NVM, I'm an idiot and I missed an import.
I created a custom batchActions
that make possible to use async actions with thunks, and also nested async actions:
https://gist.github.com/GuillaumeJasmin/3956fb03becdba50dc18ab9a721b9793 .
I use the default batchActions()
of redux-batched-actions
, but wrapped into a Promise. It work perfectly for me.
It seem to solve this issue ?
@GuillaumeJasmin At this point, I haven't used Redux in well over a year (using CycleJS with a React driver), so I'm a bit out of the mindset, but at first glance that looks like a reasonable wrapper to just include if you need it. You might look at publishing a simple module that handles wrapping this one with Promise
, with a clear interface. redux-batched-async
or something like that.
I'm hesitant to bring the Promise
bit into redux-batched-actions
only because it feels pretty specific, and makes it a bit more complicated. Thoughts?