Redux: How to chain synchronous actions?

Created on 24 Mar 2016  路  15Comments  路  Source: reduxjs/redux

This may sounds like a dumb question but I'm wondering what would be the right way to chain synchronous actions. The reason I want to do it is because I want to dispatch action B AFTER certain store value is updated by action A, which is a synchronous action (like UI interaction).

Obviously this won't work.

// assuming I've mapped state to props and bind action creators with @connect()
this.props.syncActionA(newValue); // dispatch sync action A,
// using 'reselect', calculatedValue comes from some selector functions that automatically update upon state update
if (this.props.calculatedValue === 'something') {
  this.props.actionB(); // dispatch action B
}

What I really want to do is something like

this.props.syncActionA(newValue).then(() => {
  if (this.props.calculatedValue  === 'something') {
    this.props.actionB(); 
  }
});

But apparently the dispatch function doesn't return a promise, therefore can't be then'ed.
What I ended up with doing is

new Promise((resolve) => resolve(this.props.syncActionA(newValue)))
  .then(() => {
    if (this.props.calculatedValue  === 'something') {
      this.props.actionB(); 
    }
  });

It worked, but I'm not sure if this is the correct way to do it. I'd love to hear from more experienced people on how this situation should be handled.

Most helpful comment

dispatch is synchronous by default, so maybe you can use redux-thunk + a selector to get that calculateValue:

export const actionA = (param1, param2) => (dispatch, getState) => {  
  dispatch({
    type: ACTION_A,
    param1,
    param2
  });
  // At this line of code first dispatch has been executed and the state has been updated
  const value = selectValue(getState(), param2);
  if (value === 'neededValue') {
    dispatch({
      type:   ACTION_B             
    })
  }
};

All 15 comments

@realbugger This is just my take on Redux and I may be wrong...

There are several problems with your solution:

  1. All updates to state should be testable in Redux without involving a component. Components would normally dispatch an action based on an event emitted by a component element e.g. a button click.
  2. Reselect is meant to provide a transform of the current state for use by a component. It does not update state and really should not trigger other state updates.

I think that syncActionA should incorporate the selector and the state update of actionB. If the selector output is required by your component, then store it in the state.

dispatch is synchronous by default, so maybe you can use redux-thunk + a selector to get that calculateValue:

export const actionA = (param1, param2) => (dispatch, getState) => {  
  dispatch({
    type: ACTION_A,
    param1,
    param2
  });
  // At this line of code first dispatch has been executed and the state has been updated
  const value = selectValue(getState(), param2);
  if (value === 'neededValue') {
    dispatch({
      type:   ACTION_B             
    })
  }
};

Why not just using the componentWillReceiveProps React lifecycle method ?

componentWillReceiveProps(nextProps) {
  const newValue =  nextProps.calculatedValue;

  if (newValue !== this.props.calculatedValue && newValue  === 'something') {
    this.props.actionB(); 
  }
}

@Dattaya I think your code would work, but you don't need redux-thunk because there is no asynchronous action involved.

Also, if the component needs the selector result, the selector will be called in two places - not a performance issue but not the ideal code path. Maybe the selector result could be saved to the store - but then it should be calculated in a reducer.

@ludoblues That's clever and succinct. But it somehow seems wrong for a component to dispatch an action and then dispatch another action based on the reducer result.

I still don't have the ideal solution but I warn you that using componentWillReceiveProps for that has become a major issue in the project I'm working on. If this scales to monitoring several changes or chain of chain of events, you may end up having crazy application logic baked into the component lifecycle (sometimes small state machines). We're looking into applying the side effects model to solve this (https://github.com/reactjs/redux/issues/1528).

@Dattaya , @johnsoftek, @realbugger : actually, the redux-thunk approach is EXACTLY the right answer. Thunks aren't just for running AJAX calls and other async behavior - they allow you to do _anything_ you want in there, rather than strictly synchronously returning a plain action object.

In this case, the thunk package gives access to both dispatch and getState, so per @Dattaya's example, you can call dispatch once _synchronously_, know that when it returns the state has been updated, call getState, and now check the updated values and do something else like another dispatch.

As for testability: thunks are generally considered to be not as easily testable as code using redux-saga, but you could certainly still test this. Call the action creator, take the returned function, pass in spies for dispatch and getState or something, verify they were called. Something to that effect, anyway. Might have to fake upgetState a bit so that it returns useful test data.

@andradeatwork : sounds like you're sort of interested in looking at changes to specific portions of the store? I actually just wrote up a Stack Overflow answer on that at http://stackoverflow.com/a/36214442/62937 . Short version: no built-in way to do it, so you either have to use componentWillReceiveProps like you said, or have logic attached to store.subscribe, and there's at least a couple libs out there that implement the hooks for that on top of Redux.

Thanks for all the response. I guess one thing I'm not entirely clear is the sequence of Dispatching Action, State Update, and Props Update. @Dattaya mentioned that dispatch should be synchronous by default. But in the component that dispatches syncActionA(newValue), If I console.log(this.props.value) right after the dispatch, it still shows the old value. Maybe updating state is synchronous but map to component props is not?

I agree with @johnsoftek that component should not handle such conditional dispatching logic and it's better to pack it within action creator. Like @markerikson said, with Redux-thunk, I'll get access to dispatch and getState and should be able to accomplish this. The only part I'm not sure is if I can/should access selector functions from action creators. If so, it looks like the right solution should be moving the conditional dispatching logic from component into action creator syncActionA(). Within it, after dispatching ACTION_A, I can get calculatedValue from selector functions, then dispact actionB() based on it.

@realbugger : correct, dispatch is synchronous. By the time it returns, you know that the action should have been handled by the reducers (assuming that you don't have middleware doing something to intercept an action and delay it). So, if you call getState in a thunk right after calling dispatch, you now have a reference to the updated state tree. However, React will do async batching of updates, so you definitely can't assume that a component's props are updated right away (same way that you can't call this.setState() in a component and then expect that this.state will have been updated).

You can totally use selectors inside action creators. Just import your selector functions in the file where you define your action creators, and use them: let someValue = someSelector(getState());.

Thanks everyone for your response. This is really helpful. Closing the thread now.

@realbugger Just an after thought. Interesting article by James K Nelson: http://jamesknelson.com/join-the-dark-side-of-the-flux-responding-to-actions-with-actors/.

I have used this approach to react to changes to state, particularly logged in/authorisation state, whose update paths can be quite complex. James calls these functions "actors". I call them reactions (as opposed to actions).

I think this really asks the wrong question, your store should contain the minimal required state need for the application. If you need values derived from other values in the store you should be creating selectors using reselect.

https://github.com/reactjs/reselect

I am new in redux and i feel that we can chain synchronous action if we are able to inject middleware after reducer has done the changes. Current middleware functionality sits between action and reducer, when you dispatch an action then middleware intercepts it. But i guess there is nothing like : Callback after reducer has done the changes. So if we have that callback in placed then we can give green flag to next dispatch.

@AbhishekSinghBais Just use redux-thunk.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CellOcean picture CellOcean  路  3Comments

timdorr picture timdorr  路  3Comments

parallelthought picture parallelthought  路  3Comments

captbaritone picture captbaritone  路  3Comments

amorphius picture amorphius  路  3Comments