redux-batched-actions icon indicating copy to clipboard operation
redux-batched-actions copied to clipboard

Supporting thunks in batches

Open p0rsche opened this issue 8 years ago • 26 comments

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)])

p0rsche avatar Apr 18 '16 09:04 p0rsche

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.

tshelburne avatar Apr 18 '16 13:04 tshelburne

Does not work with thunks. When the actions are functions: ss 2016-04-22 at 4 46 41 pm It calls the batchingReducer for each of them and passes function into default reducer which assumes that action is object

oluckyman avatar Apr 22 '16 13:04 oluckyman

It's tricky to add support for thunks, because they work on different level. For the alternative we can use redux-saga.

p0rsche avatar Apr 22 '16 16:04 p0rsche

I see. It would be nice to drop a few lines in the docs to cover that issue

oluckyman avatar Apr 22 '16 17:04 oluckyman

I'm going to look into it as well - @oluckyman in the meantime, if you want to submit doc updates, happy to merge it.

tshelburne avatar Apr 23 '16 12:04 tshelburne

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?

jonashartwig avatar Jun 10 '16 08:06 jonashartwig

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

syavorsky avatar Jun 12 '16 00:06 syavorsky

@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!

tshelburne avatar Jun 12 '16 22:06 tshelburne

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!

abc123s avatar Jun 20 '16 21:06 abc123s

Great, I will take a look!

jonashartwig avatar Jun 28 '16 07:06 jonashartwig

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 avatar Aug 05 '16 18:08 dpwrussell

@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! :)

tshelburne avatar Aug 08 '16 17:08 tshelburne

@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 avatar Dec 10 '16 07:12 jedwards1211

@jedwards1211 That seems like a great idea when redux-thunk is at the point of a BC break. Had never even considered that.

tshelburne avatar Dec 10 '16 15:12 tshelburne

@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.

jedwards1211 avatar Dec 10 '16 17:12 jedwards1211

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 avatar Dec 10 '16 17:12 gaearon

@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.

jedwards1211 avatar Dec 10 '16 19:12 jedwards1211

Not sure what you mean.

gaearon avatar Dec 10 '16 19:12 gaearon

@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.

jedwards1211 avatar Dec 10 '16 19:12 jedwards1211

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 avatar Dec 10 '16 19:12 gaearon

@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.

jedwards1211 avatar Dec 10 '16 19:12 jedwards1211

@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.

jedwards1211 avatar Dec 10 '16 19:12 jedwards1211

@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.

jedwards1211 avatar Dec 11 '16 20:12 jedwards1211

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.

JonnyLinja avatar Jul 29 '17 01:07 JonnyLinja

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 avatar Feb 28 '18 23:02 GuillaumeJasmin

@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?

tshelburne avatar Mar 18 '18 18:03 tshelburne