return promise from store after redux thunk dispatch
Asked Answered
A

5

79

I am trying to chain dispatches with redux thunk

function simple_action(){
  return {type: "SIMPLE_ACTION"}
}

export function async_action(){
  return function(dispatch, getState){
    return dispatch(simple_action).then(()=>{...});
  }
}

How do I get the dispatch to return a promise from the store?

MORE SPECIFICALLY:

I am probably just not understanding something here, but in all the examples with redux-thunk, they call a separate async event (like fetch), which obviously returns a Promise.

What I'm specifically looking for is when I dispatch an action to the store: How do I make certain the store has processed that action completely before anything else happens in the function action_creator() above.

Ideally, I would like the store to return some sort of promise, but I don't understand how or where that happens?

Alkalinize answered 28/1, 2016 at 18:27 Comment(0)
B
55

Here you have an example on how to dispatch and chain async action. https://github.com/gaearon/redux-thunk

The thunk middleware knows how to turn thunk async actions into actions, so you just have to have your simple_action() to be a thunk and the thunk middleware will do the job for you, if the middleware see a normal action, he will dispatch this action as normal action but if it's an async function it will turn your async action into normal action.

So your simple_action need to be a thunk ( A thunk is a function that returns a function.) Like this for example:

function makeASandwichWithSecretSauce(forPerson) {
  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

When using the makeASandwichWithSecretSauce function you can use the dispatch function

store.dispatch(
  makeASandwichWithSecretSauce('Me')
);

And even

// It even takes care to return the thunk’s return value
// from the dispatch, so I can chain Promises as long as I return them.

store.dispatch(
  makeASandwichWithSecretSauce('My wife')
).then(() => {
  console.log('Done!');
});

Here a complete example on how you can write action creators that dispatch actions and async actions from other action creators, and build your control flow with Promises.

function makeSandwichesForEverybody() {
  return function (dispatch, getState) {
    if (!getState().sandwiches.isShopOpen) {
      // You don’t have to return Promises, but it’s a handy convention
      // so the caller can always call .then() on async dispatch result.
      return Promise.resolve();
    }

    //Do this action before starting the next one below 
    dispatch(simple_action());

    // We can dispatch both plain object actions and other thunks,
    // which lets us compose the asynchronous actions in a single flow.
    return dispatch(
      makeASandwichWithSecretSauce('My Grandma')
    ).then(() =>
      Promise.all([
        dispatch(makeASandwichWithSecretSauce('Me')),
        dispatch(makeASandwichWithSecretSauce('My wife'))
      ])
    ).then(() =>
      dispatch(makeASandwichWithSecretSauce('Our kids'))
    ).then(() =>
      dispatch(getState().myMoney > 42 ?
        withdrawMoney(42) :
        apologize('Me', 'The Sandwich Shop')
      )
    );
  };
}
//apologize and withdrawMoney are simple action like this for example
      return {
        type:  "END_SUCESS"
      }

//usage

store.dispatch(
  makeSandwichesForEverybody()
).then(() =>
    console.log("Done !");
);

To create you own promises you can use a library like bluebird.

//EDIT : To be sure that the store has processed that action completely before anything else happens in the function action_creator() you can dispatch this simple_action before action_creator(); // I added this comment to the code //Do this action before starting the next one below

Bolten answered 28/1, 2016 at 23:1 Comment(14)
For me this isn't working. If I want to call then after store.dispatch() I get a TypeError: Cannot read property 'then' of undefined. My action definitely returns a promise.Xenolith
did you register the tune middleware ? github.com/gaearon/redux-thunk Installation sectionBolten
did you register the thunk middleware ? Can you provide some code to see where the problem might be ?Bolten
Inling code will be messy. In principle I have a thunk which returns a promise function call, which in turn returns a promise as well. So what happens is that store.dispatch -> thunkAction -> getStatusApi -> store.dispatch().then -> myAsyncApiCall -> finalAction -> reducer. I always return from within each promise/function and I have no idea why it doesn't wait for myAsyncApiCall to resolve.Xenolith
For me everything work fine, I get the same error if I didn't register the thunk middleware be sure to have the thunk middleware registered well : const store = createStore( rootReducer, applyMiddleware(thunk) ); and have redux version greater than 3.1.0Bolten
I tried with the latest versions, thunk middleware works fine for non-async stuff and the error remains. Maybe Angular2/zonejs interferes somehow. If I restructure my app into make the api call and then dispatch the result everything works perfectly fine.Xenolith
Maybe you are right that Angular2/zonejs interferes somehow. Good to know, I cannot help you more never tried Angular2.Bolten
I made a very simple plunk, not involving Angular at all that demonstrates the effect (open console): plnkr.co/edit/66hXPwHRi27Esp5tT79oXenolith
Issue is solved. Dan Abramov pointed out that using setTimeout won't be working and in Angular I simply missed to return dispatch in my store wrapper. Thanks for your help and time anyway.Xenolith
This solution worked out great for rehydrating state with an async DB call. I'm curious, though: would Redux Saga replace this type of implementation if you needed something more sophisticated?Delacourt
hey @Aaleks. What are you thoughts on this solution after a year? Would you or do you still use it or is there anything else your prefer?Appreciate
This method is exactly what I'm doing and it works great except for one case - if the action is already in progress from some other caller, I don't want to repeat the same async call, so I instead return a resolved promise. The problem with this is the second caller now gets a resolved promise, but the async call is still in progress. How can the second caller wait for the first async call to finish? The store could hold the original promise, but then the store would end up being mutated outside the reducers. Ideas?Cybernetics
This answer is plagiarized almost verbatim from Facebooks example. At the very least attribution should be given, or this answer removed.Hemi
Got an example for if you use mapDispatchToProps?Missioner
T
12

This is a pattern I've been using recently:

export const someThenableThunk = someData => (dispatch, getState) => Promise.resolve().then(() => {
  const { someReducer } = getState();
  return dispatch({
    type: actionTypes.SOME_ACTION_TYPE,
    someData,
  });
});

When you dispatch(someThenableThunk('hello-world')), it returns a Promise object that you can chain further actions to.

Tittup answered 21/7, 2018 at 9:40 Comment(5)
interesting +1 technique.Yunyunfei
simple and powerfulRosanne
Fantastic, and exactly what I needed.. out of curiosity, are there any downsides to this pattern?Wellappointed
@JohnDetlefs Awesome! Not that I've found. It's likely less performant than synchronous solutions. I use it basically everywhere. It helps enforce a common convention I use now, where state modifiers (i.e. actions) are specifically promises and state analysis are conventional synchronous getters. This way it helps enforce separation and consistency.Tittup
@Tittup - Ta for the followup, love that pattern, I suspect I'll be using a lot from now on. 👍Wellappointed
G
5

dispatch will return whatever the action/function it calls returns; so if you want to chain certain activities (as per your example), your action would need to return a Promise.

As @Aaleks mentions, if your action were a thunk you can create a scenario where you return a Promise, then you could do as you mention.

BTW I think naming your thunk action_creator is a bit misleading, as simple_action is actually an Action Creator in Redux parlance - have edited accordingly :)

Gomar answered 24/11, 2016 at 18:15 Comment(0)
T
4

Asynchronous action and how to call an action from a component when using redux and thunk

Without Promise

action.js

export function shareForm(id) {
    return function (dispatch) {
        dispatch({
            type: 'SHARE_FORM',
            payload: source.shareForm(id)
        })
    }
}

SomeComponent.js

dispatch(shareForm(id))

With Promise

action.js

export function shareForm(id, dispatch) {
    return new Promise((resolve, reject) => {
        dispatch({
            type: 'SHARE_FORM',
            payload: source.shareForm(id)
        })
          .then(res => resolve(res))
          .catch(err => reject(err))
    })
}

SomeComponent.js

shareForm(id, dispatch)
  .then(res => console.log('log on success', res))
  .catch(err => console.log('log on failure', err))

PS: Let me know in comments if you need more explanations

Turbulence answered 27/7, 2022 at 6:42 Comment(1)
what comes in res in "with promise" in action.js example? .then(res => resolve(res))Sunwise
A
3

What you will need to do is create trunkate action which returns Promise. The dispatch function return what you have added as argument to it's call. For example, if you want dispatch to return Promise you'd have to add Promise as argument to the call.

function simple_action() {
  return { type: 'SIMPLE_ACTION' };
}

export function async_action(dispatch, getState) {
  return function () {
    return Promise.resolve(dispatch(simple_action()));
  }
}

const boundAction = async_action(dispatch, getState);
boundAction().then(() => {});
Anguilliform answered 21/2, 2021 at 19:6 Comment(1)
We have the same approach due to simplicity in implementation. You can either return Promise resolve or reject and let the caller do a then for resolve, catch for reject -- which for me maintains the chain. At the same time, you are returning the dispatch object containing possible payload from api. But, not sure if this conforms to best practices. Anyone? I kinda don't like the idea that the caller will handle success/failure inside then(). Failure should be handled in catch.Photomural

© 2022 - 2024 — McMap. All rights reserved.