Batching actions #911

Closed
tshelburne opened this Issue Oct 18, 2015 · 12 comments

Comments

3 participants
@tshelburne

I needed to be able to mark specific actions as "skipped" so that a larger action could encapsulate smaller ones without notifying subscribers of incoherent state. I created redux-skip-by-action for this purpose - unit tests are pending, QA tests passing, and any feedback would be really appreciated.

Thanks.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Oct 19, 2015

Collaborator

In my view this hurts predictability.
What is the use case compared to just firing one action instead of many?

Collaborator

gaearon commented Oct 19, 2015

In my view this hurts predictability.
What is the use case compared to just firing one action instead of many?

@gaearon gaearon added the ecosystem label Oct 19, 2015

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Oct 19, 2015

Collaborator

Instead, the “Redux approach” to this would be to create a batching action creator:

function batchActions(...actions) {
  return {
    type: 'BATCH_ACTIONS',
    actions: actions
  };
}

// usage
store.dispatch(
  batchActions(
    doSomething(),
    doSomethingElse()
  )
);

and a higher order reducer:

function enableBatching(reducer) {
  return function batchingReducer(state, action) {
    switch (action.type) {
    case 'BATCH_ACTIONS':
      return action.actions.reduce(batchingReducer, state);
    default:
      return reducer(state, action);
    }
  }
}

// usage
let store = createStore(enableBatching(reducer));
Collaborator

gaearon commented Oct 19, 2015

Instead, the “Redux approach” to this would be to create a batching action creator:

function batchActions(...actions) {
  return {
    type: 'BATCH_ACTIONS',
    actions: actions
  };
}

// usage
store.dispatch(
  batchActions(
    doSomething(),
    doSomethingElse()
  )
);

and a higher order reducer:

function enableBatching(reducer) {
  return function batchingReducer(state, action) {
    switch (action.type) {
    case 'BATCH_ACTIONS':
      return action.actions.reduce(batchingReducer, state);
    default:
      return reducer(state, action);
    }
  }
}

// usage
let store = createStore(enableBatching(reducer));

@gaearon gaearon added the docs label Oct 19, 2015

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Oct 19, 2015

Collaborator

We might want to add this to the docs..

Collaborator

gaearon commented Oct 19, 2015

We might want to add this to the docs..

@fubhy

This comment has been minimized.

Show comment
Hide comment
@fubhy

fubhy Oct 19, 2015

I solved this with a middleware that acts on action yielding generators.

fubhy commented Oct 19, 2015

I solved this with a middleware that acts on action yielding generators.

@tshelburne

This comment has been minimized.

Show comment
Hide comment
@tshelburne

tshelburne Oct 19, 2015

@gaearon I have a situation where I need to set a property (action creator 1), validate via external service the new value of the property (action creator 2), get the initial state via external service of another object based on that property, and then set that object in the state as well (action creator 3), and then finally validate the entirety of the new state via another external service (action creator 4). The place where things fell apart for me was the external service parts - if I allow subscribers to receive a notification after action 1, they receive an incoherent state, but I kept finding myself pushed to make the async calls in the reducers in order to move in the direction you described.

If I'm reading your suggestion right, I basically need to create a list of actions to run at the end of a promise chain that gets dispatched all at once. Rather than dispatching actions to the store to get updated state, I use getState initially and run my reducer manually against that "copy" to build the list of simple object actions that will ultimately be run in the store. Does this sound right?

This all felt a bit manual to me before, but I think that does pretty drastically improve the predictability of things.

@gaearon I have a situation where I need to set a property (action creator 1), validate via external service the new value of the property (action creator 2), get the initial state via external service of another object based on that property, and then set that object in the state as well (action creator 3), and then finally validate the entirety of the new state via another external service (action creator 4). The place where things fell apart for me was the external service parts - if I allow subscribers to receive a notification after action 1, they receive an incoherent state, but I kept finding myself pushed to make the async calls in the reducers in order to move in the direction you described.

If I'm reading your suggestion right, I basically need to create a list of actions to run at the end of a promise chain that gets dispatched all at once. Rather than dispatching actions to the store to get updated state, I use getState initially and run my reducer manually against that "copy" to build the list of simple object actions that will ultimately be run in the store. Does this sound right?

This all felt a bit manual to me before, but I think that does pretty drastically improve the predictability of things.

@tshelburne

This comment has been minimized.

Show comment
Hide comment
@tshelburne

tshelburne Oct 19, 2015

@gaearon That's a much better solution - thanks for the direction, feel free to close this.

@gaearon That's a much better solution - thanks for the direction, feel free to close this.

@tshelburne

This comment has been minimized.

Show comment
Hide comment
@tshelburne

tshelburne Oct 19, 2015

Also, note that the arguments in your higher order reducer for the passthrough are reversed - should be reducer(state, action)

Also, note that the arguments in your higher order reducer for the passthrough are reversed - should be reducer(state, action)

@tshelburne

This comment has been minimized.

Show comment
Hide comment
@tshelburne

tshelburne Oct 19, 2015

I also noticed that it's better to use batchingReducer recursively to enable combining batched actions into larger groups.

function enableBatching(reducer) {
  return function batchingReducer(state, action) {
    switch (action.type) {
    case 'BATCH_ACTIONS':
      return action.actions.reduce(batchingReducer, state);
    default:
      return reducer(state, action);
    }
  }
}

I also noticed that it's better to use batchingReducer recursively to enable combining batched actions into larger groups.

function enableBatching(reducer) {
  return function batchingReducer(state, action) {
    switch (action.type) {
    case 'BATCH_ACTIONS':
      return action.actions.reduce(batchingReducer, state);
    default:
      return reducer(state, action);
    }
  }
}
@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Oct 19, 2015

Collaborator

Good points. Want to publish this as a package?

Collaborator

gaearon commented Oct 19, 2015

Good points. Want to publish this as a package?

@tshelburne

This comment has been minimized.

Show comment
Hide comment
@tshelburne

tshelburne Oct 19, 2015

Will do - I'll take care of it tonight and ping you.

Will do - I'll take care of it tonight and ping you.

@tshelburne

This comment has been minimized.

Show comment
Hide comment
@tshelburne

tshelburne Oct 19, 2015

Added to GH and published on npm - let me know your thoughts: https://github.com/tshelburne/redux-batched-actions

Added to GH and published on npm - let me know your thoughts: https://github.com/tshelburne/redux-batched-actions

@gaearon

This comment has been minimized.

Show comment
Hide comment
Collaborator

gaearon commented Oct 20, 2015

@gaearon gaearon closed this Oct 20, 2015

@gaearon gaearon changed the title from Add 'redux-skip-by-action' to Ecosystem to Batching actions Oct 27, 2015

This was referenced Oct 27, 2015

@scozv scozv referenced this issue in scozv/scozv.github.com Mar 10, 2016

Open

a post on How to React a editing form #13

@wiredprogrammer wiredprogrammer referenced this issue in ngrx/store Jul 27, 2016

Closed

Batching actions in ngrx #178

@voidxnull voidxnull referenced this issue in Lokiedu/libertysoil-site Sep 28, 2016

Closed

Get rid of multiple dispatch invocations #663

@earnubs earnubs referenced this issue in canonical-websites/build.snapcraft.io Nov 29, 2016

Merged

GitHub repository input component #8

7 of 7 tasks complete

@jimbol jimbol referenced this issue in redux-saga/redux-saga Sep 7, 2017

Closed

Batching `put`s #1161

@adrianabreu adrianabreu referenced this issue in etsiiull/SIMDE Feb 23, 2018

Closed

Remove callbacks and integrate redux #1

4 of 4 tasks complete

@jonaskello jonaskello referenced this issue in redux-loop/redux-loop May 31, 2018

Open

Cmd.action without dispatching #187

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment