manaflair / redux-batch

Enhance your Redux store to support batched actions
174 stars 12 forks source link

Error using redux-batch with thunks #8

Closed nealoke closed 7 years ago

nealoke commented 7 years ago

I'm using your middleware which really is the best I could find! 👍

The only question I have is how to use this together with redux-thunk? I currently get the following error when I try to batch stuff action creators like this.

What I'm trying to batch

export const receiveAccount = (accountData) => (dispatch) => {
    // Insert questions
    accountData.questions.map(question => dispatch(insertQuestion(question)));

    return dispatch({
        type: ActionTypes.RECEIVE_ACCOUNT,
        payload: accountData
    });
};

Error log Uncaught (in promise) Error: Actions must be plain objects. Use custom middleware for async actions.

Store setup export default createStore(reducers, composeEnhancers(reduxBatch, applyMiddleware(ReduxThunk), reduxBatch));

johanneslumpe commented 7 years ago

I'm running into the same issues and it seems that the actual problem is dispatching thunks within thunks, when the thunk is part of a batch.

Given this setup:

const store = createStore(
  reducer,
  compose(reduxBatch, applyMiddleware(thunk), reduxBatch)
);

The following works and will dispatch plainAction twice:

const plainAction = { type: 'test' };
const thunkB = dispatch => dispatch(plainAction);
const thunkA = dispatch => dispatch(thunkB);

store.dispatch([plainAction, thunkA]);

Whereas this fails:

const plainAction = { type: 'test' };
const thunkB = dispatch => dispatch(plainAction);
const thunkA = dispatch => dispatch([thunkB]);

store.dispatch([plainAction, thunkA]);

The only difference here is that in the second example thunkB is dispatched within a batch from thunkA. The issue seems to be that the 2nd enhancer receives the raw store.dispatch function and thus has no ability to handle thunks. Dispatching a non-batched thunk from a thunk within a 1st level batch works fine, because the thunk middleware itself can resolve the thunk instantly. It doesn't know how to handle batches though, so it passes them on, resulting in this issue.

arcanis commented 7 years ago

Unfortunately that's more a Redux issue than redux-batch :/ If an action is handled by a middleware, it will never be able to dispatch an action that has to be handled by a previous middleware. Only next ones will be aware of this newly-generated action.

I think you might be able to do the following, but of course it's not very generic since it would only support two levels of thunks:

const store = createStore(
  reducer,
  compose(reduxBatch, applyMiddleware(thunk), reduxBatch, applyMiddleware(thunk), reduxBatch)
);
johanneslumpe commented 7 years ago

@arcanis Yeah I agree. The problem is inherently that something else than just a plain action is being dispatched. Solutions like sagas or epics are much more adequate for this because they only act upon generic actions and nothing special.

nealoke commented 7 years ago

@johanneslumpe @arcanis yep seems clear. This is closed for now I guess. Thanks for the input guys 👍

swarthy commented 6 years ago

With applyMiddleware you can use something like

export function batchMiddleware() {
  return function(next) {
    return function dispatchRecurse(action) {
      return Array.isArray(action)
        ? action.map(subAction => dispatchRecurse(subAction))
        : next(action)
    }
  }
}

In combination with redux-batch enhancer subscribers will be notified only once, and your array actions will be dispatched through other middlewares sorry for my english, I hope idea is understandable

cyrilchapon commented 5 years ago

Status of this ?