Why do we need middleware for async flow in Redux?

193,142

Solution 1

What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?

There is nothing wrong with this approach. It’s just inconvenient in a large application because you’ll have different components performing the same actions, you might want to debounce some actions, or keep some local state like auto-incrementing IDs close to action creators, etc. So it is just easier from the maintenance point of view to extract action creators into separate functions.

You can read my answer to “How to dispatch a Redux action with a timeout” for a more detailed walkthrough.

Middleware like Redux Thunk or Redux Promise just gives you “syntax sugar” for dispatching thunks or promises, but you don’t have to use it.

So, without any middleware, your action creator might look like

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

But with Thunk Middleware you can write it like this:

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

So there is no huge difference. One thing I like about the latter approach is that the component doesn’t care that the action creator is async. It just calls dispatch normally, it can also use mapDispatchToProps to bind such action creator with a short syntax, etc. The components don’t know how action creators are implemented, and you can switch between different async approaches (Redux Thunk, Redux Promise, Redux Saga) without changing the components. On the other hand, with the former, explicit approach, your components know exactly that a specific call is async, and needs dispatch to be passed by some convention (for example, as a sync parameter).

Also think about how this code will change. Say we want to have a second data loading function, and to combine them in a single action creator.

With the first approach we need to be mindful of what kind of action creator we are calling:

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

With Redux Thunk action creators can dispatch the result of other action creators and not even think whether those are synchronous or asynchronous:

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

With this approach, if you later want your action creators to look into current Redux state, you can just use the second getState argument passed to the thunks without modifying the calling code at all:

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

If you need to change it to be synchronous, you can also do this without changing any calling code:

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

So the benefit of using middleware like Redux Thunk or Redux Promise is that components aren’t aware of how action creators are implemented, and whether they care about Redux state, whether they are synchronous or asynchronous, and whether or not they call other action creators. The downside is a little bit of indirection, but we believe it’s worth it in real applications.

Finally, Redux Thunk and friends is just one possible approach to asynchronous requests in Redux apps. Another interesting approach is Redux Saga which lets you define long-running daemons (“sagas”) that take actions as they come, and transform or perform requests before outputting actions. This moves the logic from action creators into sagas. You might want to check it out, and later pick what suits you the most.

I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past.

This is incorrect. The docs said this, but the docs were wrong.
Action creators were never required to be pure functions.
We fixed the docs to reflect that.

Solution 2

You don't.

But... you should use redux-saga :)

Dan Abramov's answer is right about redux-thunk but I will talk a bit more about redux-saga that is quite similar but more powerful.

Imperative VS declarative

  • DOM: jQuery is imperative / React is declarative
  • Monads: IO is imperative / Free is declarative
  • Redux effects: redux-thunk is imperative / redux-saga is declarative

When you have a thunk in your hands, like an IO monad or a promise, you can't easily know what it will do once you execute. The only way to test a thunk is to execute it, and mock the dispatcher (or the whole outside world if it interacts with more stuff...).

If you are using mocks, then you are not doing functional programming.

Seen through the lens of side-effects, mocks are a flag that your code is impure, and in the functional programmer's eye, proof that something is wrong. Instead of downloading a library to help us check the iceberg is intact, we should be sailing around it. A hardcore TDD/Java guy once asked me how you do mocking in Clojure. The answer is, we usually don't. We usually see it as a sign we need to refactor our code.

Source

The sagas (as they got implemented in redux-saga) are declarative and like the Free monad or React components, they are much easier to test without any mock.

See also this article:

in modern FP, we shouldn’t write programs — we should write descriptions of programs, which we can then introspect, transform, and interpret at will.

(Actually, Redux-saga is like a hybrid: the flow is imperative but the effects are declarative)

Confusion: actions/events/commands...

There is a lot of confusion in the frontend world on how some backend concepts like CQRS / EventSourcing and Flux / Redux may be related, mostly because in Flux we use the term "action" which can sometimes represent both imperative code (LOAD_USER) and events (USER_LOADED). I believe that like event-sourcing, you should only dispatch events.

Using sagas in practice

Imagine an app with a link to a user profile. The idiomatic way to handle this with each middleware would be:

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>

function loadUserProfile(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
      err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
    );
}

redux-saga

<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>


function* loadUserProfileOnNameClick() {
  yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}

function* fetchUser(action) {
  try {
    const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
    yield put({ type: 'USER_PROFILE_LOADED', userProfile })
  } 
  catch(err) {
    yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
  }
}

This saga translates to:

every time a username gets clicked, fetch the user profile and then dispatch an event with the loaded profile.

As you can see, there are some advantages of redux-saga.

The usage of takeLatest permits to express that you are only interested to get the data of the last username clicked (handle concurrency problems in case the user click very fast on a lot of usernames). This kind of stuff is hard with thunks. You could have used takeEvery if you don't want this behavior.

You keep action creators pure. Note it's still useful to keep actionCreators (in sagas put and components dispatch), as it might help you to add action validation (assertions/flow/typescript) in the future.

Your code becomes much more testable as the effects are declarative

You don't need anymore to trigger rpc-like calls like actions.loadUser(). Your UI just needs to dispatch what HAS HAPPENED. We only fire events (always in the past tense!) and not actions anymore. This means that you can create decoupled "ducks" or Bounded Contexts and that the saga can act as the coupling point between these modular components.

This means that your views are more easy to manage because they don't need anymore to contain that translation layer between what has happened and what should happen as an effect

For example imagine an infinite scroll view. CONTAINER_SCROLLED can lead to NEXT_PAGE_LOADED, but is it really the responsibility of the scrollable container to decide whether or not we should load another page? Then he has to be aware of more complicated stuff like whether or not the last page was loaded successfully or if there is already a page that tries to load, or if there is no more items left to load? I don't think so: for maximum reusability the scrollable container should just describe that it has been scrolled. The loading of a page is a "business effect" of that scroll

Some might argue that generators can inherently hide state outside of redux store with local variables, but if you start to orchestrate complex things inside thunks by starting timers etc you would have the same problem anyway. And there's a select effect that now permits to get some state from your Redux store.

Sagas can be time-traveled and also enables complex flow logging and dev-tools that are currently being worked on. Here is some simple async flow logging that is already implemented:

saga flow logging

Decoupling

Sagas are not only replacing redux thunks. They come from backend / distributed systems / event-sourcing.

It is a very common misconception that sagas are just here to replace your redux thunks with better testability. Actually this is just an implementation detail of redux-saga. Using declarative effects is better than thunks for testability, but the saga pattern can be implemented on top of imperative or declarative code.

In the first place, the saga is a piece of software that permits to coordinate long running transactions (eventual consistency), and transactions across different bounded contexts (domain driven design jargon).

To simplify this for frontend world, imagine there is widget1 and widget2. When some button on widget1 is clicked, then it should have an effect on widget2. Instead of coupling the 2 widgets together (ie widget1 dispatch an action that targets widget2), widget1 only dispatch that its button was clicked. Then the saga listen for this button click and then update widget2 by dispaching a new event that widget2 is aware of.

This adds a level of indirection that is unnecessary for simple apps, but make it more easy to scale complex applications. You can now publish widget1 and widget2 to different npm repositories so that they never have to know about each others, without having them to share a global registry of actions. The 2 widgets are now bounded contexts that can live separately. They do not need each others to be consistent and can be reused in other apps as well. The saga is the coupling point between the two widgets that coordinate them in a meaningful way for your business.

Some nice articles on how to structure your Redux app, on which you can use Redux-saga for decoupling reasons:

A concrete usecase: notification system

I want my components to be able to trigger the display of in-app notifications. But I don't want my components to be highly coupled to the notification system that has its own business rules (max 3 notifications displayed at the same time, notification queueing, 4 seconds display-time etc...).

I don't want my JSX components to decide when a notification will show/hide. I just give it the ability to request a notification, and leave the complex rules inside the saga. This kind of stuff is quite hard to implement with thunks or promises.

notifications

I've described here how this can be done with saga

Why is it called a Saga?

The term saga comes from the backend world. I initially introduced Yassine (the author of Redux-saga) to that term in a long discussion.

Initially, that term was introduced with a paper, the saga pattern was supposed to be used to handle eventual consistency in distributed transactions, but its usage has been extended to a broader definition by backend developers so that it now also covers the "process manager" pattern (somehow the original saga pattern is a specialized form of process manager).

Today, the term "saga" is confusing as it can describe 2 different things. As it is used in redux-saga, it does not describe a way to handle distributed transactions but rather a way to coordinate actions in your app. redux-saga could also have been called redux-process-manager.

See also:

Alternatives

If you don't like the idea of using generators but you are interested by the saga pattern and its decoupling properties, you can also achieve the same with redux-observable which uses the name epic to describe the exact same pattern, but with RxJS. If you're already familiar with Rx, you'll feel right at home.

const loadUserProfileOnNameClickEpic = action$ =>
  action$.ofType('USER_NAME_CLICKED')
    .switchMap(action =>
      Observable.ajax(`http://data.com/${action.payload.userId}`)
        .map(userProfile => ({
          type: 'USER_PROFILE_LOADED',
          userProfile
        }))
        .catch(err => Observable.of({
          type: 'USER_PROFILE_LOAD_FAILED',
          err
        }))
    );

Some redux-saga useful resources

2017 advises

  • Don't overuse Redux-saga just for the sake of using it. Testable API calls only are not worth it.
  • Don't remove thunks from your project for most simple cases.
  • Don't hesitate to dispatch thunks in yield put(someActionThunk) if it makes sense.

If you are frightened of using Redux-saga (or Redux-observable) but just need the decoupling pattern, check redux-dispatch-subscribe: it permits to listen to dispatches and trigger new dispatches in listener.

const unsubscribe = store.addDispatchListener(action => {
  if (action.type === 'ping') {
    store.dispatch({ type: 'pong' });
  }
});

Solution 3

The short answer: seems like a totally reasonable approach to the asynchrony problem to me. With a couple caveats.

I had a very similar line of thought when working on a new project we just started at my job. I was a big fan of vanilla Redux's elegant system for updating the store and rerendering components in a way that stays out of the guts of a React component tree. It seemed weird to me to hook into that elegant dispatch mechanism to handle asynchrony.

I ended up going with a really similar approach to what you have there in a library I factored out of our project, which we called react-redux-controller.

I ended up not going with the exact approach you have above for a couple reasons:

  1. The way you have it written, those dispatching functions don't have access to the store. You can somewhat get around that by having your UI components pass in all of the info the dispatching function needs. But I'd argue that this couples those UI components to the dispatching logic unnecessarily. And more problematically, there's no obvious way for the dispatching function to access updated state in async continuations.
  2. The dispatching functions have access to dispatch itself via lexical scope. This limits the options for refactoring once that connect statement gets out of hand -- and it's looking pretty unwieldy with just that one update method. So you need some system for letting you compose those dispatcher functions if you break them up into separate modules.

Take together, you have to rig up some system to allow dispatch and the store to be injected into your dispatching functions, along with the parameters of the event. I know of three reasonable approaches to this dependency injection:

  • redux-thunk does this in a functional way, by passing them into your thunks (making them not exactly thunks at all, by dome definitions). I haven't worked with the other dispatch middleware approaches, but I assume they're basically the same.
  • react-redux-controller does this with a coroutine. As a bonus, it also gives you access to the "selectors", which are the functions you may have passed in as the first argument to connect, rather than having to work directly with the raw, normalized store.
  • You could also do it the object-oriented way by injecting them into the this context, through a variety of possible mechanisms.

Update

It occurs to me that part of this conundrum is a limitation of react-redux. The first argument to connect gets a state snapshot, but not dispatch. The second argument gets dispatch but not the state. Neither argument gets a thunk that closes over the current state, for being able to see updated state at the time of a continuation/callback.

Solution 4

Abramov's goal - and everyone's ideally - is simply to encapsulate complexity (and async calls) in the place where it's most appropriate and reusable.

Where's the best place to do that in the standard Redux dataflow? How about:

  • Reducers? No way. They should be pure functions with no side-effects. Updating the store is serious, complicated business. Don't contaminate it.
  • Dumb View Components? Definitely No. They have one concern: presentation and user-interaction, and should be as simple as possible.
  • Container Components? Possible, but sub-optimal. It makes sense in that the container is a place where we encapsulate some view related complexity and interact with the store, but:
    • Containers do need to be more complex than dumb components, but it's still a single responsibility: providing bindings between view and state/store. Your async logic is a whole separate concern from that.
    • By placing it in a container, you'd be locking your async logic into a single context, coupled to one or more views/routes. Bad idea. Ideally it's all reusable, and totally decoupled from the views.
    • (Like all rules, there could be an exception if you have stateful binding logic that happens to be reusable across multiple contexts, or if you can somehow generalize all of your state into something like an integrated GraphQL schema. OK, fine, that could be cool. But... most of the time the bindings seem to end up pretty context/view specific.)
  • Some other Service Module? Bad idea: you'd need to inject access to the store, which is a maintainability/testability nightmare. Better to go with the grain of Redux and access the store only using the APIs/models provided.
  • Actions and the Middlewares that interpret them? Why not?! For starters, it's the only major option we have left. :-) More logically, the action system is decoupled execution logic that you can use from anywhere. It's got access to the store and can dispatch more actions. It has a single responsibility which is to organize the flow of control and data around the application, and most async fits right into that.
    • What about the Action Creators? Why not just do async in there, instead of in the actions themselves, and in Middleware?
      • First and most important, the creators don't have access to the store, as middleware does. That means you can't dispatch new contingent actions, can't read from the store to compose your async, etc.
      • So, keep complexity in a place that's complex of necessity, and keep everything else simple. The creators can then be simple, relatively pure functions that are easy to test.

Solution 5

To answer the question that is asked in the beginning:

Why can't the container component call the async API, and then dispatch the actions?

Keep in mind that those docs are for Redux, not Redux plus React. Redux stores hooked up to React components can do exactly what you say, but a Plain Jane Redux store with no middleware doesn't accept arguments to dispatch except plain ol' objects.

Without middleware you could of course still do

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

But it's a similar case where the asynchrony is wrapped around Redux rather than handled by Redux. So, middleware allows for asynchrony by modifying what can be passed directly to dispatch.


That said, the spirit of your suggestion is, I think, valid. There are certainly other ways you could handle asynchrony in a Redux + React application.

One benefit of using middleware is that you can continue to use action creators as normal without worrying about exactly how they're hooked up. For example, using redux-thunk, the code you wrote would look a lot like

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

which doesn't look all that different from the original — it's just shuffled a bit — and connect doesn't know that updateThing is (or needs to be) asynchronous.

If you also wanted to support promises, observables, sagas, or crazy custom and highly declarative action creators, then Redux can do it just by changing what you pass to dispatch (aka, what you return from action creators). No mucking with the React components (or connect calls) necessary.

Share:
193,142
sbichenko
Author by

sbichenko

Updated on July 08, 2022

Comments

  • sbichenko
    sbichenko almost 2 years

    According to the docs, "Without middleware, Redux store only supports synchronous data flow". I don't understand why this is the case. Why can't the container component call the async API, and then dispatch the actions?

    For example, imagine a simple UI: a field and a button. When user pushes the button, the field gets populated with data from a remote server.

    A field and a button

    import * as React from 'react';
    import * as Redux from 'redux';
    import { Provider, connect } from 'react-redux';
    
    const ActionTypes = {
        STARTED_UPDATING: 'STARTED_UPDATING',
        UPDATED: 'UPDATED'
    };
    
    class AsyncApi {
        static getFieldValue() {
            const promise = new Promise((resolve) => {
                setTimeout(() => {
                    resolve(Math.floor(Math.random() * 100));
                }, 1000);
            });
            return promise;
        }
    }
    
    class App extends React.Component {
        render() {
            return (
                <div>
                    <input value={this.props.field}/>
                    <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                    {this.props.isWaiting && <div>Waiting...</div>}
                </div>
            );
        }
    }
    App.propTypes = {
        dispatch: React.PropTypes.func,
        field: React.PropTypes.any,
        isWaiting: React.PropTypes.bool
    };
    
    const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
        switch (action.type) {
            case ActionTypes.STARTED_UPDATING:
                return { ...state, isWaiting: true };
            case ActionTypes.UPDATED:
                return { ...state, isWaiting: false, field: action.payload };
            default:
                return state;
        }
    };
    const store = Redux.createStore(reducer);
    const ConnectedApp = connect(
        (state) => {
            return { ...state };
        },
        (dispatch) => {
            return {
                update: () => {
                    dispatch({
                        type: ActionTypes.STARTED_UPDATING
                    });
                    AsyncApi.getFieldValue()
                        .then(result => dispatch({
                            type: ActionTypes.UPDATED,
                            payload: result
                        }));
                }
            };
        })(App);
    export default class extends React.Component {
        render() {
            return <Provider store={store}><ConnectedApp/></Provider>;
        }
    }
    

    When the exported component is rendered, I can click the button and the input is updated correctly.

    Note the update function in the connect call. It dispatches an action that tells the App that it is updating, and then performs an async call. After the call finishes, the provided value is dispatched as a payload of another action.

    What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?

    EDIT: I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past. For example, here's a user trying to provide a better explanation for async data flow:

    The action creator itself is still a pure function, but the thunk function it returns doesn't need to be, and it can do our async calls

    Action creators are no longer required to be pure. So, thunk/promise middleware was definitely required in the past, but it seems that this is no longer the case?

  • Sergey Lapin
    Sergey Lapin over 8 years
    Maybe short way to say Dan's thought is: middleware is centralized approach, this way allow you to keep your components simpler and generalized and control data flow in one place. If you maintain big app you will enjoy it=)
  • acjay
    acjay over 8 years
    I'm probably missing something, but why is it true that "your components know exactly that a specific call is async"? Would it not be possible to assume that every call is possibly async and always pass dispatch (and getState, for that matter)? Not trying to nitpick, but if this is the case, then it's really just a question of dependency injection, and there's no first-order reason that the DI should happen in dispatch middleware. This was where I was coming from github.com/rackt/react-redux/issues/237, in which Dan alluded some of the second-order concerns.
  • acjay
    acjay over 8 years
    This discussion is also relevant to other ways to peel the onion: github.com/rackt/redux/issues/1182#issuecomment-168795954, with some observations by others on why it might make sense to keep action creators synchronous and pure, other than the mostly aesthetic reasons that have been on my mind. That said, the async action creator approach absolutely seems to be where the momentum is, so more power to Dan in this answer.
  • catamphetamine
    catamphetamine over 8 years
    "thunk" approach won't work when you need to show an alert() after the action completion. Promises do. I currently recommend the Promises approach.
  • catamphetamine
    catamphetamine over 8 years
    You advice to just dispatch yet another event on action completion. That won't work when you need to show an alert() after the action completion. Promises inside React components do work though. I currently recommend the Promises approach.
  • Dan Abramov
    Dan Abramov over 8 years
    @asdfasdfads I don't see why it wouldn't work. It would work exactly the same; put alert after dispatch()ing the action.
  • Søren Debois
    Søren Debois over 8 years
    Why do I need to pass in dispatch and getState? If the premise of redux is that there is a single global store, why don't I just use that, i.e., call store.dispatch whenever I need to dispatch an action?
  • Dan Abramov
    Dan Abramov over 8 years
    @SørenDebois I don't understand the question. Maybe you can create a new Q for that?
  • Søren Debois
    Søren Debois over 8 years
    Penultimate line in your very first code example: loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch. Why do I need to pass in dispatch? If by convention there's ever only a single global store, why don't I just do reference that directly and do store.dispatch whenever I need to, e.g., in loadData?
  • Dan Abramov
    Dan Abramov over 8 years
    @SørenDebois If your app is client side only that would work. If it's rendered on the server, you'll want to have a different store instance for every request so you can't define it beforehand.
  • RainerAtSpirit
    RainerAtSpirit about 8 years
    This is getting better each time I revisit. Consider turning it into a blog post :).
  • Sebastien Lorber
    Sebastien Lorber about 8 years
    thanks @RainerAtSpirit I'm going to but I want to figure out some stuff before, including solving this Elm-architecture question (github.com/jarvisaoieong/redux-architecture/issues/1) and doing some code with these ideas in github.com/slorber/todomvc-onboarding
  • ThorbenA
    ThorbenA about 8 years
    @SebastienLorber great answer! You ask actually do we still need [action creators] at all?. What would we still need them for? I cant come up with good reason, do you?
  • Sebastien Lorber
    Sebastien Lorber about 8 years
    @ThorbenA i can't find a good reason either but have not yet experienced it enough with these concepts in a real-world application. We are only starting to use redux-saga on my app so time will tell :)
  • Sergey Sinkovskiy
    Sergey Sinkovskiy about 8 years
    What happens with 'simple' actions, do you need to create sagas for them too?
  • Sebastien Lorber
    Sebastien Lorber about 8 years
    @SergeySinkovskiy I don't know what you mean by "simple action". Sagas do not forbid you to do simple synchronous action dispatchs. And you are still free to perform simple data loading calls with componentDidMount and setState, or use thunks if you think it's simpler or require less boilerplate than sagas. Just be aware of pros and cons of each approach, but you can mix them if it makes sense to you.
  • Sergey Sinkovskiy
    Sergey Sinkovskiy about 8 years
    @SebastienLorber thanks for your answer. It would be great to see this as a blog post indeed.
  • faceyspacey.com
    faceyspacey.com about 8 years
    I'm curious about @acjay's point: "Would it not be possible to assume that every call is possibly async and always pass dispatch (and getState, for that matter)?" But more importantly, it seems it would give us the benefit of using ES7 async and await. For example, you would dispatch that you're loading, then make a seemingly synchronous call using await and then dispatch your results. It's basically the async/await version of @sbichenko 's example. How can we achieve that without manually passing dispatch? I can't find any middleware that offers it. Perhaps redux-thunk does already?
  • Dan Abramov
    Dan Abramov about 8 years
    Some people prefer to use other async primitives rather than functions accepting getState and dispatch. For example some people use sagas, others use customized thunks with dependency injection, others use CSP channels, etc. So we don’t want to do this by default. As for async/await, it works inside thunks just fine so I don’t really see how this is related.
  • swelet
    swelet almost 8 years
    Thank you for a good write up. However I don't agree on certain aspects. How is LOAD_USER imperative? To me, it's not only declarative - it also gives great readable code. Like eg. "When I press this button I want to ADD_ITEM". I can look at the code and understand exactly what's going on. If it instead were called something to the effect of "BUTTON_CLICK", I'd have to look that up.
  • swennemen
    swennemen almost 8 years
    NIce answer. There is another alternative now: github.com/blesh/redux-observable
  • V-SHY
    V-SHY almost 8 years
    I wonder how you know the 123 is action.payload.userId for the redux-saga example, it seem like not declare anywhere.
  • adriendenat
    adriendenat almost 8 years
    I completely agree with @swelet I feel like the redux-thunk example is more understandable than the redux-saga one as you can easily see from the UI component (trigering the action) what is happening and that something external to this component is called. Whereas, like often with events, you have no idea where in your app things are reacting to the event you just triggered.
  • heisenberg
    heisenberg almost 8 years
    I agree with @ericpeters0n , this was a big pain point for me because I thought I had to use thunks or other middleware.
  • Guy
    Guy almost 8 years
    Just want to point out that this answer has 139 lines which is 9.92 times more than the source code of redux-thunk which consists of 14 lines: github.com/gaearon/redux-thunk/blob/master/src/index.js
  • gskema
    gskema over 7 years
    I just want to point out that loadSomeData and loadOtherData without redux-thunk are called "action creators", but they return 'Promise'. So I assume when we pass them to dispatcher, they don't reach the reducers, but only produce side effects (dispatch new actions) after some time. So we shouldn't mix them together with standard sync action creators?
  • Sebastien Lorber
    Sebastien Lorber over 7 years
    @swelet sorry for late anwer. When you dispatch ADD_ITEM, it's imperative because you dispatch an action that aims to have an effect on your store: you expect the action to do something. Being declarative embrace the philosophy of event-sourcing: you don't dispatch actions to trigger changes on your applications, but you dispatch past events to describe what has happened into your application. The dispatch of an event should be sufficient to consider that the state of the application has changed. The fact that there's a Redux store that reacts to event is an optional implementation detail
  • happy.cze
    happy.cze over 7 years
    I think, that there is no need to use such middlewares like redux-thunk if you use binded action creators, but maybe i'm missing something...
  • TeoTN
    TeoTN over 7 years
    I consider one thing as quite confusing: I disagree in a great extent with an argument, that not following funtional paradigms is worse - since both are just paradigms and the concept of programming paradigm is just a matter of constraints that a programmer follows. Being consistent is quite important but suggesting that by doing mocks you don't obey functional programming paradigm and implicitly this makes something worse is, in my opinion, just wrong. Programming paradigms are no better one than the other, their just means of achieving goals and there's no single "better" way.
  • Sebastien Lorber
    Sebastien Lorber over 7 years
    @TeoTN We are discussing a concrete usecase here. I'm not saying any paradigm is better than another globally (and use OOP/imperative paradigms when it makes sense), but if you think about this concrete usecase and you disagree that FP / declarative effects approach is better, please tell me which paradigm is better and why. Here I explained why FP paradigm is better for sagas because it leverages better testability than imperative paradigm. Still, you can implement the saga pattern with imperative logic: I did that in the past before Redux-saga existed.
  • Daniel T.
    Daniel T. about 7 years
    I didn't understand async redux until reading this answer. (I mean, I understood the technical how, but not the abstract why.) Thanks.
  • Sheldon Papa
    Sheldon Papa almost 7 years
    @DanAbramov how about Saga? which one is better?
  • Wylliam Judd
    Wylliam Judd almost 7 years
    @SergeyLapin, I've heard people say that redux-thunk lets you keep everything in one place, but I don't see how that's true. You can keep all of your asyc methods in one file without redux-thunks just fine.
  • Bhojendra Rauniyar
    Bhojendra Rauniyar about 6 years
    @DanAbramov I am learning redux and came to this point. But I'm still in confusion what middleware should I use? I tried a hard research to analyze differences between them but not able to find anything. I would be glad if you could answer this question: stackoverflow.com/questions/50035984/…
  • ZenMaster
    ZenMaster almost 6 years
    Have gone through this multiple times over the years... Still am not clear as to why redux-thunk is worse for the notification use case. You dispatch an action at the end of a thunk... dispatch(userCreated(userInfo) and then store (via connect) updates the "notification engine". Same approach exactly. Oh... and since async/await .., thunk looks better and more intuitive to a vast majority of people I've talked to.
  • Sebastien Lorber
    Sebastien Lorber almost 6 years
    @ZenMaster the notification example is more complexe on the linked page and to achieve the same you would have to deal with complex timeout/interval setup with thunks and get a result difficult to test. Also don't forget the primary goal of sagas is decoupling initially, only for complex apps, the rest is bonus. I don't recommend using sagas on small monolith apps or to avoid thunks when it's a simpler tool from the job. In fact most of my apps use thunks/promise Middleware first and introduce sagas on a case by case basis
  • Estus Flask
    Estus Flask over 5 years
    Container Components - why not? Due to the role components play in React, a container may act as service class, and it already gets a store via DI (props). By placing it in a container, you'd be locking your async logic into a single context, for a single view/route - how so? A component can have multiple instances. It can be decoupled from presentation, e.g. with render prop. I guess the answer could benefit even more from short examples that prove the point.
  • Abhinav Manchanda
    Abhinav Manchanda over 5 years
    I don't like this answer because it distracts from the actual question in order to market someones own library. This answer provides a comparison of the two libraries, which was not the intent of the question. The actual question is asking whether to use middleware at all, which is explained by the accepted answer.
  • Bhojendra Rauniyar
    Bhojendra Rauniyar over 5 years
    First time on SO, did not read anything. But just liked the post gazing the picture. Amazing, hint, and reminder.
  • serkan
    serkan about 5 years
    'It’s just inconvenient in a large application', if you organize your application big, this is your first mistake and this popular excuse is definitely not an answer. 'Oh when its big...' Horsecrap.
  • zameb
    zameb over 4 years
    This is serious stuff for someone that just wanna call an API endpoint to return an entity or list of entities. You recommend, "just do this... then this, then this, then this other thing, then that, then this other stuff, then continue, then do..". But man, this is FRONTEND, we just need to call the BACKEND to give us data ready to be used on the frontend. If this is the way to go, something is wrong, something is really wrong and somebody is not applying KISS nowadays
  • SM Chinna
    SM Chinna over 4 years
    Hi, Use try and catch block for the API Calls. Once API has given the response, call the Reducer action types.
  • Wils
    Wils over 4 years
    Thank you! I am wondering if we could get an updated answer...
  • nonopolarity
    nonopolarity about 4 years
    The very first version of not using a thunk doesn't have to use a return at the very beginning...
  • jorisw
    jorisw about 4 years
    @zameb You may be right, but your complaint, then, is with Redux itself, and all the overheard it brings while trying to reduce complexity.
  • spedy
    spedy over 3 years
    I don't think calling getState inside actions is good practice, use of function arguments should be the prefered way. Arguments define how the function should behave under different conditions. In your case the function is not indempotent and changes under various unknown conditions. Can give some serious headaches to developers who try to reuse the function.
  • Dilip Agheda
    Dilip Agheda over 3 years
    this is a great answer