Redux: Trying to put API calls in the correct place

Created on 20 Jul 2015  ·  115Comments  ·  Source: reduxjs/redux

I'm trying to make a login success/error flow but my main concern is where I can put this logic.

Currently I'm using actions -> reducer (switch case with action calling API) -> success/error on response triggering another action.

The problem with this approach is that the reducer is not working when I call the action from the API call.

am I missing something?

Reducer

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';
import Immutable from 'immutable';
import LoginApiCall from '../utils/login-request';

const initialState = new Immutable.Map({
  email: '',
  password: '',
}).asMutable();

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      console.log(action.user);
      LoginApiCall.login(action.user);
      return state;
    case LOGGED_FAILED:
      console.log('failed from reducer');
      return state;
    case LOGGED_SUCCESSFULLY:
      console.log('success', action);
      console.log('success from reducer');
      break;
    default:
      return state;
  }
}

actions

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return dispatch => {
    dispatch({ error, type: LOGGED_FAILED });
  };
}

/*
 * Should add the route like parameter in this method
*/
export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard'); // will fire CHANGE_ROUTE in its change handler
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return dispatch => {
    dispatch({ user, type: LOGIN_ATTEMPT });
  };
}

API calls

 // Use there fetch polyfill
 // The main idea is create a helper in order to handle success/error status
import * as LoginActions from '../actions/LoginActions';

const LoginApiCall = {
  login(userData) {
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        LoginActions.loginSuccess(response);
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        LoginActions.loginError();
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
  },
};

export default LoginApiCall;
docs question

Most helpful comment

Finally, where do you put the login API call then?

This is precisely what dispatch => {} action creator is for. Side effects!

It's just another action creator. Put it together with other actions:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

export function login(userData) {
  return dispatch =>
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        dispatch(loginSuccess(response));
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        dispatch(loginError(error));
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
}

In your components, just call

this.props.login(); // assuming it was bound with bindActionCreators before

// --or--

this.props.dispatch(login()); // assuming you only have dispatch from Connector

All 115 comments

This is almost correct, but the problem is _you can't just call pure action creators and expect things to happen_. Don't forget your action creators are just functions that specify _what_ needs to be dispatched.

// CounterActions
export function increment() {
  return { type: INCREMENT }
}


// Some other file
import { increment } from './CounterActions';
store.dispatch(increment()); <--- This will work assuming you have a reference to the Store
increment(); <---- THIS DOESN'T DO ANYTHING! You're just calling your function and ignoring result.


// SomeComponent
import { increment } from './CounterActions';

@connect(state => state.counter) // will inject store's dispatch into props
class SomeComponent {
  render() {
    return <OtherComponent {...bindActionCreators(CounterActions, this.props.dispatch)} />
  }
}


// OtherComponent
class OtherComponent {
  handleClick() {
    // this is correct:
    this.props.increment(); // <---- it was bound to dispatch in SomeComponent

    // THIS DOESN'T DO ANYTHING:
    CounterActions.increment(); // <---- it's just your functions as is! it's not bound to the Store.
  }
}

Now, let's get to your example. The first thing I want to clarify is you don't need async dispatch => {} form if you only dispatch a single action synchronously and don't have side effects (true for loginError and loginRequest).

This:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return dispatch => {
    dispatch({ error, type: LOGGED_FAILED });
  };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return dispatch => {
    dispatch({ user, type: LOGIN_ATTEMPT });
  };
}

can be simplified as

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

// You'll have a side effect here so (dispatch) => {} form is a good idea
export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

Secondly, your reducers are supposed to be _pure functions that don't have side effects_. Do not attempt to call your API from a reducer. Reducer is synchronous and passive, it only modifies the state.

This:

const initialState = new Immutable.Map({
  email: '',
  password: '',
  isLoggingIn: false,
  isLoggedIn: false,
  error: null
}).asMutable(); // <---------------------- why asMutable?

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      console.log(action.user);
      LoginApiCall.login(action.user); // <------------------------ no side effects in reducers! :-(
      return state;
    case LOGGED_FAILED:
      console.log('failed from reducer');
      return state;
    case LOGGED_SUCCESSFULLY:
      console.log('success', action);
      console.log('success from reducer');
      break;
    default:
      return state;
  }

should probably look more like

const initialState = new Immutable.Map({
  email: '',
  password: '',
  isLoggingIn: false,
  isLoggedIn: false,
  error: null
});

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      return state.merge({
        isLoggingIn: true,
        isLoggedIn: false,
        email: action.email,
        password: action.password // Note you shouldn't store user's password in real apps
      });
    case LOGGED_FAILED:
      return state.merge({
        error: action.error,
        isLoggingIn: false,
        isLoggedIn: false
      });
    case LOGGED_SUCCESSFULLY:
      return state.merge({
        error: null,
        isLoggingIn: false,
        isLoggedIn: true
      });
      break;
    default:
      return state;
  }

Finally, where do you put the login API call then?

This is precisely what dispatch => {} action creator is for. Side effects!

It's just another action creator. Put it together with other actions:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

export function login(userData) {
  return dispatch =>
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        dispatch(loginSuccess(response));
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        dispatch(loginError(error));
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
}

In your components, just call

this.props.login(); // assuming it was bound with bindActionCreators before

// --or--

this.props.dispatch(login()); // assuming you only have dispatch from Connector

Finally, if you find yourself often writing large action creators like this, it's a good idea to write a custom middleware for async calls compatible with promises are whatever you use for async.

The technique I described above (action creators with dispatch => {} signature) is right now included in Redux, but in 1.0 will be available only as a separate package called redux-thunk. While you're at it, you might as well check out redux-promise-middleware or redux-promise.

@gaearon :clap: that was an amazing explanation! ;) It should definitely make it into the docs.

@gaearon awesome explanation! :trophy:

@gaearon What if you need to perform some logic to determine which API call should be called? Isn't it domain logic which should be in one place(reducers)? Keeping that in Action creators sounds to me like breaking single source of truth. Besides, mostly you need to parametrise the API call by some value from the application state. You most likely also want to test the logic somehow. We found making API calls in Reducers (atomic flux) very helpful and testable in large scale project.

We found making API calls in Reducers (atomic flux) very helpful and testable in large scale project.

This breaks record/replay. Functions with side effects are harder to test than pure functions by definition. You can use Redux like this but it's completely against its design. :-)

Keeping that in Action creators sounds to me like breaking single source of truth.

“Single source of truth” means data lives in one place, and has no independent copies. It doesn't mean “all domain logic should be in one place”.

What if you need to perform some logic to determine which API call should be called? Isn't it domain logic which should be in one place(reducers)?

Reducers specify how state is transformed by actions. They shouldn't worry about where these actions originate. They may come from components, action creators, recorded serialized session, etc. This is the beauty of the concept of using actions.

Any API calls (or logic that determines which API is called) happens before reducers. This is why Redux supports middleware. Thunk middleware described above lets you use conditionals and even read from the state:

// Simple pure action creator
function loginFailure(error) {
  return { type: LOGIN_FAILURE, error };
}

// Another simple pure action creator
function loginSuccess(userId) {
  return { type: LOGIN_SUCCESS, userId };
}

// Another simple pure action creator
function logout() {
  return { type: LOGOUT };  
}


// Side effect: uses thunk middleware
function login() {
  return dispatch => {
    MyAwesomeAPI.performLogin().then(
      json => dispatch(loginSuccess(json.userId)),
      error => dispatch(loginFailure(error))
    )
  };
}


// Side effect *and* reads state
function toggleLoginState() {
  return (dispatch, getState) => {
    const { isLoggedIn } = getState().loginState;
    if (isLoggedIn) {
      dispatch(login());
    } else {
      dispatch(logout());
    }
  };
}

// Component
this.props.toggleLoginState(); // Doesn't care how it happens

Action creators and middleware are _designed_ to perform side effects and complement each other.
Reducers are just state machines and have nothing to do with async.

Reducers specify how state is transformed by actions.

This is indeed very valid point, thanks.

I'll reopen for posterity until a version of this is in the docs.

:+1:

I would argue that while reducers are state machines, so are action creators in general (but implicitly).

Say user clicked "submit" button twice. First time you will send HTTP request to the server (in the action creator) and change state to "submitting" (in the reducer). Second time you do not whant to do that.

In current model your action creator will have to choose what actions to dispatch depending on current state. If it is not currently "submitting" - then send HTTP request and dispatch one action, otherwise - just do nothing or even dispatch different action (like warning).

So your control flow is effectively split into two state machines and one of them is implicit and full of side effects.

I'd say that this Flux approach handles the majority of use-cases of typical web app very well. But when you have complex control logic it may become problematic. All of existing examples just do not have complex control logic, so this problem is somewhat hidden.

https://github.com/Day8/re-frame is an example of different approach where they have single event handler which acts as the only FSM.

But then reducers have side effects. So it is interesting how they deal with "replay" feature in such cases - asked them in https://github.com/Day8/re-frame/issues/86

In general, I think this is a real problem and it's not a surprise that it appears again and again. It's interesting to see what solutions will emerge eventually.

Keep us posted about re-frame and side effects!

In my book there are 3 state types in any decent Redux web app.

1) View components (React this.state). This should be avoided but sometimes I just want to implement some behavior which can be reused by just reusing a component independently of Redux.

2) App shared state, i. e. Redux.state. It is state a view layer is using to present an app to a user and view components use it to "communicate" between each other. And this state can be used to "communicate" between ActionCreators, Middlewares, views as they decisions can depend on this state. Hence the "shared" is important. It doesn't have to be a complete app state though. Or I think it is impractical to implement everything as this type of state (in terms of actions).

3) State which is held by other side-effecting complex modules/libs/services. I would write these to handle scenarios vladar is describing. Or react-router is another example. You can treat it as a black-box which has its own state or you can decide to lift part of react-router state into app shared state (Redux.state). If I would write a complex HTTP module which would cleverly handle all my requests and its timings, I'm usually not interested in request timing details in Redux.state. I would only use this module from ActionCreators/Middlewares, possibly together with Redux.state to get new Redux.state.

If I would like to write view component showing my request's timings I would have to get this state to Redux.state.

@vladar Is it what you mean that AC/MW are implicit state machines? That because they don't held state by itself but they are still dependent on state held somewhere else and that they can define control logic how this states evolve in time? For some cases, I think they still could be implemented as closures and held its own state, becoming explicit state machines?

Alternatively I could call Redux.state a "public state", while other states are private. How to design my app is an act to decide what to keep as a private state and what as a public state. It seems to me like nice opportunity for encapsulation, hence I don't see it problematic to have state divided into different places as far as it doesn't become a hell how they affect each other.

@vladar

But then reducers have side effects. So it is interesting how they deal with "replay" feature in such cases

To achieve easy replay the code has to be deterministic. It is what Redux achieves by requiring pure reducers. In an effect Redux.state divides app into non-deterministic (async) and deterministic parts. You can replay bahavior above Redux.state assuming you don't do crazy thing in view components. The first important goal to achieve deterministic code is to move async code away and translate it into synchronous code via action log. It is what Flux architecture does in general. But in general it is not enough and other side-effecting or mutating code can still break deterministic processing.

Achieving replay-ability with side-effecting reducers is imho either impractically hard, even impossible or it will work only partially with many corner cases with probably a little practical effect.

To achieve easy timetravel we store snapshots of app state instead of replaying actions (which is always hard) as done in Redux.

To achieve easy timetravel we store snapshots of app state instead of replaying actions (which is always hard) as done in Redux.

Redux DevTools store both snapshots and actions. If you don't have actions, you can't hot reload the reducers. It's the most powerful feature of the workflow that DevTools enable.

Time travel works fine with impure action creators because _it happens on the level of the dispatched (final) raw actions_. These are plain objects so they're completely deterministic. Yes, you can't “rollback” an API call, but I don't see a problem with that. You can rollback the raw actions emitted by it.

That because they don't held state by itself but they are still dependent on state held somewhere else and that they can define control logic how this states evolve in time?

Yeah, that's exactly what I mean. But you may also end up with duplication of your control logic. Some code to demonstrate the problem (from my example above with double submit click).

function submit(data) {
  return (dispatch, getState) => {
    const { isSubmitting } = getState().isSubmitting;
    if (!isSubmitting) {
      dispatch(started(data));

      MyAPI.submit(data).then(
        json => dispatch(success(json)),
        error => dispatch(failure(error))
      )
    }
  }
}

function started(data) {
   return { type: SUBMIT_STARTED, data };
}

function success(result) {
  return { type: SUBMIT_SUCCESS, result };
}

function failure(error) {
  return { type: SUBMIT_FAILURE, error };
}

And then you have your reducer to check "isSubmitting" state again

const initialState = new Immutable.Map({
  isSubmitting: false,
  pendingData: null,
  error: null
});

export default function form(state = initialState, action) {
  switch (action.type) {
    case SUBMIT_STARTED:
      if (!state.isSubmitting) {
        return state.merge({
          isSubmitting: true,
          pendingData: action.data
        });
      } else {
        return state;
      }
  }
}

So you end-up having same logical checks in two places. Obviously duplication in this example is minimal, but for more complex scenarious it may get not pretty.

I think in the last example the check should _not_ be inside the reducer. Otherwise it can quickly descend into inconsistency (e.g. action is dispatched by mistake, but ignored, but then the “success” action is also dispatched but it was unexpected, and state might get merged incorrectly).

(Sorry if that was the point you were making ;-)

I agree with gaeron because the !isSubmitting is already encoded in the fact that SUBMIT_STARTED action has been dispatched. When an action is a result of some logic than that logic should not be replicated in reducer. Then reducer responsibility is just that when anytime it receives SUBMIT_STARTED it doesn't think and just updates state with action payload because somebody else had taken the responsibility to decide that SUBMIT_STARTED.

One should always aim that there is a single thing which takes responsibility for a single decision.

Reducer can then further build on SUBMIT_STARTED fact and extend it with additional logic but this logic should be different. F.e.:

case SUBMIT_STARTED:
  if (goodMood) loading...
  else nothing_happens

I can imagine that it can be sometimes tricky to decide who should be responsible for what. ActionCreator, Middleware, standalone complex module, reducer?

I would aim to have as much decisions as possible in reducers while keeping them pure, because they can be hot-reloaded. If decisions are required for side-effects/async then they go to AC/MW/somewhere_else. It can as well depend how best practices evolve regarding code-reuse, i.e. reducers vs. middleware.

AC can't mutate state.isSubmitting directly. Hence, if its logic depends on it AC needs guarantees that state.isSubmitting will be in sync with its logic. If AC logic wants to set state.isSubmitted to true with new data and reads is back it expects it is set as such. There shouldn't by other logic potentially changing this premise. ACTION is the sync primitive itself. Basically actions define protocol and protocols should be well and clearly defined.

But I think I know what you are trying to say. Redux.state is shared state. Shared state is always tricky. It is easy to write code in reducers which changes state in a way which is unexpected by AC logic. Hence I can imagine it can be hard to keep logic between AC and reducers in sync in some very complex scenarios. This can lead to bugs which might be hard to follow and debug. I believe that for such scenarios it is always possible to collapse the logic to AC/Middleware and make reducers only stupidly act based on well defined actions with no or little logic.

Somebody can always add new reducer which will break some old dependent AC. One should think twice before making AC dependent on Redux.state.

I have an action creator that requires an authentication token returned by an API request with user credentials. There is a time limit on this token and it needs to be refreshed and managed by something. What you're proposing seems to indicate that this state doesnt belong in Redux.state?

Where should it go? Conceptually does it get passed into the constructor of the Redux store as immutable outer-state?

Hence I can imagine it can be hard to keep logic between AC and reducers in sync in some very complex scenarios.

Yep. I am not saying that it's a show-stopper, but it may become inconsistent. The point of state machine is to react to events - regardless of order of their appearence - and stay consistent.

If you rely on proper ordering of events from AC - you just implicitly move parts of your state engine to AC and couple it with reducer.

Maybe it's just a matter of preference, but I feel much better when store / reducer always acts consistently and doesn't rely on something external to keep consistency somewhere else.

One should think twice before making AC dependent on Redux.state.

I think you can't avoid it in complex cases (without moving some side effects to reducers/event-handlers).

Looks like it is a tradeoff: "dropping hot-reloading or replay feature" vs "managing state in one place". In later case you can write real FSMs or Statecharts like re-frame proposes, in former case it's more ad-hoc control logic as we have in Flux now.

I have read (too quickly) through Re-frame and it is the same sort of thing as Redux with some implementation details differences. Re-frame view components are based on observables, Events are Actions, Re-frame dispatches events (actions) directly as objects (they are constructed in a view component) without using creators, Event Handlers are pure and they are the same thing as Redux reducers.

Handling side-effects isn't discussed much or I missed that, but what I'm assuming is that they are handled in Middlewares. And there is the main difference. Middlewares wrap Event handlers (reducers), compose with them from outside, while Redux Middlewares don't wrap reducers. In other words Re-frame composes side-effects directly to pure reducers while Redux keeps them separated.

It means that one can't record Re-frame events and easily replay them.

When I was talking about possible inconsistencies, I considered shared state as the root cause and I believe Re-frame has the same risk.

The difference is in coupling Middlewares with reducers. When Middlewares finish in Re-frame they directly pass result to event handlers (reducers), this result is not recorded as event/action, hence not replayable. I would say Redux just puts hooks in between instead in form of actions, hence I believe I can technically achieve the same what Re-frame does, with more flexibility at the expense of more typing (creating actions) and probably more room to do it wrong.

In the end there is nothing is Redux stopping me to implement exactly the same approach as in Re-frame. I can create any function which take reducer as a parameter, do some processing in it then directly call reducer function with the result and call it Reducer Middlewares or whatever. It is just about if I want to make it a bit more easier at the expense of loosing DevTools and more coupling.

I still have to think more if there is an significant advantage in Re-frame approach above some simplification (decreasing amount of actions). I'm open to prove me wrong, as I have said I read to quickly through it.

Actually, I was a bit incorrect. The difference is not in Redux vs Re-frame implementations, but really in "where you put your side effects".

If you do this in event handlers (reducers) and disallow action creators to affect your control flow, then technically there is no difference - you can use fsm / statecharts in Redux as well and have your control flow in one place.

But in reality - there is a difference. @gaearon explained it very well: Redux expects your reducers to be pure, otherwise replay feature breaks. So you are not supposed to do that (putting side effects in reducers), as it's against Redux design.

Re-frame also states that event handlers are pure, but even in Readme they mention that HTTP requests are initiated in event handlers. So this is a bit unclear for me, but looks like their expectations are different: you can put your side effects in event-handlers.

Then it's interesting how they approach replay (and that's what I asked at https://github.com/Day8/re-frame/issues/86).

I assume the only way to do it when your event handlers can have side effects is what @tomkis1 mentioned - recording state returned by every event handler (not re-run reducers on every event).

Hot reloading can still work, except that it cannot affect "past" events - only produce some new branch of state in time.

So the difference in design is probably subtle, but as for me it is important.

I think they do HTTP requests by composing pure event handlers with side-effecting middlewares. This composition returns new event handler which is not pure anymore but the inner one stays pure. I don't think they suggests to build event handlers which mix app-db (state) mutation and side-effects. It makes code more testable.

If you record result of an event handler you can't make it hot-reloadable which means you can change its code on the fly. You would have to record result of middleware and that would be exactly the same what Redux does - record this result as an action and pass it to reducer (listen to it).

I think I could say that Re-frame middlewares are proactive while Redux allows reactivity (any ad-hoc reducer can listen to a result of middleware/ac). One thing I'm starting to realize is that AC as mostly used in Flux and middlewares are the same thing as controllers.

As I understood @tomkis1 suggests timetravel via saving state, probably on given intervals as I assume it could have perf impact if done on every single mutation.

Ok, realizing that reducers/ event handlers return complete new state, hence you said the same.

@merk I will try to write my thoughte about it probably on Monday as I probably don't get here during a weekend.

Re-frame docs recommend talking to server in event handlers: https://github.com/Day8/re-frame#talking-to-a-server

So this is where it diverges from Redux. And this difference is deeper than it looks at first glance.

Hm, I'm failing to see the difference from Redux:

The initiating event handlers should organise that the on-success or on-fail handlers for these HTTP requests themselves simply dispatch a new event. They should never attempt to modify app-db themselves. That is always done in a handler.

Replace the event with Action. Redux just have a special name for a pure event handler - a reducer. I think I'm missing something or my brain is down this weekend.

I could smell more significant difference how events are executed - queued and async. I think Redux executes actions sync and lock-stepped action by action to guarantee state consistency. It can further affects replayability as I'm not sure if just saving events and replaying them again and again will result in the same end state. Disregarding that I can't just do that because I don't know which events triggers side-effects unlike in Redux where actions always triggers pure code.

@vladap To summarize the difference as I see it:

1.

  • in Redux you initiate server request in action creator (see @gaearon reply to original question of this issue); your reducers must be pure
  • in Re-frame you do it in event handler (reducer); your event-handlers (reducers) may have side effects

2.

  • In first case your control flow is split between AC and reducer.
  • In second case all of your control flow is in one place - event-handler (reducer).

3.

  • If your control flow is split - you need coordination between AC and reducer (even if it is implicit): AC reads state produced by reducer; reducer assumes proper ordering of events from AC. Technically you introduce coupling of AC and reducer, because changes to reducer may also affect AC and vise versa.
  • If your control flow is in one place - you don't need additional coordination + you can use tools like FSMs or Statecharts in your event-handlers/reducers


    1. Redux way of enforcing pure reducers and having side-effects moved to ACs enables hot-reloading and replay

  • Re-frame way of having side effects in event-handlers (reducers) closes the door to replay as it is done in Redux (by re-evaluating reducers), but other (probably less powerfull) options are still possible - as @tomkis1 mentioned

As for difference in dev experience - it only appears when you have many async stuff (timers, parallel http requests, web sockets, etc). For sync stuff it is all same (since there is no control flow in ACs).

I guess I need to prepare some real-world example of such complex scenario to make my point more clear.

I might drive you mad, I'm sorry for that but we don't agree on one basic thing. Maybe I should read re-frame examples code, I will learn Clojure one day. Maybe you did, hence you know more. Thanks for you effort.

1.

in Redux you initiate server request in action creator (see @gaearon reply to original question of this issue); your reducers must be pure
in Re-frame you do it in event handler (reducer); your event-handlers (reducers) may have side effects

Doc I made bold explicitly says that I should initiate webapi call in an event handler and then dispatch event in its on-success. I don't understand how I can handle this dispatched event in the same event handler. This on-success event is dispatched to the router (eq. of dispatcher I think) and I need yet another event handler to handle it. This second event handler is pure and is an equivalent of Redux reducer, the first one is equivalent of ActionCreator. To me it is the same thing or I clearly miss some important insight.

As for difference in dev experience - it only appears when you have many async stuff (timers, parallel http requests, web sockets, etc). For sync stuff it is all same (since there is no control flow in ACs).

We don't agree on this one. Async stuff actually doesn't matter, you can't replay them in Redux hence you have no dev experience here. The difference appears when you have many complex pure reducers. In Redux you can take state A, some action sequence, reducer, replay it and get state B. The you keep everything the same but you make code change to the reducer and hot reload it, replay the same action sequence from state A and you get state C, immediately seeing impact of your code change. You can't do it with @tomkis1 approach.

You might even build test scenarios on top of it. Save some initial state, save some action sequence, reduce action sequence to state A, get state B and assert it. Then make changes to code which you expect to result in the same state B, replay test scenario to assert it is true and your changes have not break your app. I don't say it is the best testing approach but it can be done.

Actually I would be quite surprised if Clojurists whos I consider almost as die hard practitioners of functional programming as Haskellers would recommend to mix side-effects with code which could be pure at least without some level of composition.

Lazy evaluation/reactivity is the basic technique how to move side-effecting code as far as possible from otherwise pure code. Lazy evaluation is how Haskell finally applied impractical functional purity concept to get a program which does something practical, because completely pure program can't do much. Using monadic composition and others the whole program is created as data flow and to keep the last step in pipeline pure and never actually call side-effects in your code, program returns description what should be done. It is passed to runtime which executes it lazily - you don't trigger the execution by an imperative call. At least it is how I understand functional programming from bird's level view. We don't have such a runtime we simulate it with ActionCreators and reactivity. Disclaimer, don't take it granted, it is what I have understood from not so much reading about FP to be any kind of authority here. I might be off but I actually believe that I understood the point.

I don't understand how I can handle this dispatched event in the same event handler.

I am not saying that. By "side effects" I mean having HTTP request initiated in event handler (reducer). Performing IO is also side effect, even if it doesn't affect return of your function. I don't mean "side effects" in terms of modifying state directly in success / error handlers.

This second event handler is pure and is an equivalent of Redux reducer, the first one is equivalent of ActionCreator.

I don't think that the first one is equivalent of Action Creator. Otherwise why do you need Action Creators to do this at all? If you can do the same in reducer?

You can't do it with @tomkis1 approach.

Agree. And that's what I meant when I wrote "other (probably less powerfull) options".

I don't think that the first one is equivalent of Action Creator. Otherwise why do you need Action Creators to do this at all? If you can do the same in reducer?

ActionCreators are used to isolate side-effects from pure app (which can be replayed). As Redux is designed you can't (shouldn't) perform side-effects in reducers. You need another construct where you can place side-effecting code. In Redux these constructs are AC or middlewares. The only difference I see in re-frame is what middlewares do and that re-frame doesn't have middlewares to transform events (actions) before handlers are triggered.

Instead of AC/middlewares actually anything can be used - util module, services (like Angular services), you can build it as a separate lib which you then interface by either AC or middleware and translate its API to actions. If you ask me AC are not the right place to place this code in. AC should be just simple creators/factories constructing action objects from arguments. If I would want to be a purist it would be better to place side-effecting code strictly to middlewares. ActionCreator is a bad term for kind of a controller responsibility. But if this code is just a one-liner webapi call then there is a tendency to just simply put it inside AC and for simple app it can be fine.

AC, middlewares, 3rd party libs forms a layer I like to call a service layer. Original Facebook WebApiUtils would be part of this layer, same as if you would use react-router and listen to its changes and translate them to actions to update Redux.state, Relay can be used as service layer (than use Redux for app/view state). As most 3rd party libs are currently programmed they don't play well with replay. Obviously I don't want rewrite everything from scratch so it would. How I like to think about it is that these libs, services, utils etc. extends browser environment forming together a platform on top of which I can build my replayable app. This platform is full of side-effects, actually it is the place where I intentionally move side-effects to. I translate this platform api to actions and my app indirectly connects to this platform by listening to these action objects (trying to simulate Haskell approach which I tried to describe in a post above). This is a kind of a hack in functional programming how to still achieve purity (possibly not in an absolute academical sense) but trigger side-effects.

There is another thing which confuses me but I could misunderstood you. I think you said that for a complex logic it is benefitial to have everything in one place. I think quite contrary.

This whole actions business is similar in concept as DOM events. I really wouldn't like to mix my business code with a code how f. e. browser detects mousemove event and I'm sure you wouldn't either. Fortunately, I'm used to work on top of addListener('mousemove', ...) because these responsibilities are well separated. The point is to achieve this good separation of concerns for my business logic as well. ActionCreators, middlewares are tools for it.

Imagine that I would be writing app against an obsolete webapi which doesn't map well to my business needs. To get required data I would have to call couple of endpoints, use their result to create next calls then merge all result into a canonical form I would use in Redux.state. This logic wouldn't leak into my reducers so that I don't have to transform data in my reducers again and again. It would be in middlewares. Effectively I would isolate messy obsolete api and develop my business app as it would be written against a nice api. Once done, then maybe I would get a change to rebuilt our webapi and then I would just rewrite my middleware.

Hence having everything in one place seems easy for an easy app. But quite contrary for complex thing I see a good separation of concerns benefitial. But maybe I haven't understood your point or use case you refer to.

Actually, probably I would move the as much of data transformation to its canonical form to reducers and use reducers composition instead as it would give me replay and testability for this code which is generally pure.

I don't understand how I can handle this dispatched event in the same event handler.
I am not saying that.

I thought you want to mix webapi calls with reducers logic to have it on one place. What I'm saying that you don't do it in re-frame either because they suggest to dispatch event on success.

There can be situation when webapi -> a lot of logic -> webapi -> a lot of logic -> ... -> result for reducer, the whole chain triggered by a single click. In such cases most of the logic would be probably in AC, I wouldn't divide it till all side-effects finish. Is this what you refer to?

The best I can do here is move as much of logic to pure functions for testability but they would be called in AC scope.

It don't think I like a possibility to do it as a series of indirectly connected event handlers. It would be promise or observables chain.

More experienced Reduxers could have different opinion on that thought (@gaearon, @johanneslumpe, @acdlite, @emmenko ...)

@vladap I think this conversation moved too far away from the subject of this issue. You already mentioned the main point of my comments here:

Hence I can imagine it can be hard to keep logic between AC and reducers in sync in some very complex scenarios.

Quick example:

Situation 1: You have a list of top-10 blog authors somewhere on the page. Now you deleted a category of posts in blog. On successful delete you need to update this list of authors from the server to make it up-to-date.

Situation 2: You are on a different page. It doesn't have list of authors, but it has list of top-10 comments. You can also delete some category of blog posts and will have to update list of comments on successful delete.

So how do we handle this on the level of state-engine? (we could also use React hooks for some help, but good state engine should be able to stay consistent on it's own)

Options:
A) we put this logic in AC. Then AC will touch CategoryApi (to delete), will read from userList state and commentList state (to check which lists present in state now), talk to UserListApi and/or CommentListApi (to refresh lists) + dispatch TOP_AUTHORS_UPDATED and/or TOP_COMMENTS_UPDATED. So it will basically touch 3 different domains.

B) we put it in event handlers userList and commentList. These handlers would both listen to DELETE_CATEGORY_SUCCESS event, then call their API service and in turn dispatch TOP_AUTHORS_UPDATED / TOP_COMMENTS_UPDATED event. So each handler only touches services / state of it's own domain.

This example is probably too simplistic, but even at this level things get less pretty with API calls in ACs.

The difference comes from a fact that unlike event handlers in re-frame, ActionCreators are proactive in handling business logic. And only one AC can be executed based on DOM event. This top-level AC can call other ACs though. There can be separate ACs for UserListApi and CommentListApi so that domains are better separated, but there always has to be AC (controller like) which wires them. This part is quite a classic imperative code while re-frame is fully event-based. With a bit of work it can be replaced with a preferred approach, event-based, observables, cps etc.

@vladap It would be interesting to see if another approach is viable: when reducers can have side effects, but can also isolate them so that they could be ignored on replay.

Say reducers signature would change to: (state, action) => (state, sideEffects?) where sideEffects is a closure. Then depending on context framework could either evaluate these sideEffects or ignore them (in case of replay).

Then the example from original issue description would look like this:

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      return [state, () => {LoginApiCall.login(action.user)}];
    case LOGGED_FAILED:
      // do something
      return state;
    case LOGGED_SUCCESSFULLY:
      // do something else
     return state;
    default:
      return state;
  }
}

(everything else stays pretty much the same)

At least your control flow is in one place, your side effects are always simple (because they just create another action/event in async handlers, and logic stays in reducers - so more of your code will be replayed).

You can also write regular FSM or Statechart-like code (with composition of reducers/event-handlers)

Also you don't need to return closure with pre-defined signature in action creators.

Not sure what problems this approach could potentially cause, but as for me it's worth playing with.

Say reducers signature would change to: (state, action) => (state, sideEffects?) where sideEffects is a closure. Then depending on context framework could either evaluate these sideEffects or ignore them (in case of replay).

This is actually similar to what I heard Elm doing with (State, Action) => (State, Request?). I haven't seen any examples yet but if somebody wants to explore this please feel free to.

Yet another approach to possibly get some inspiration from is an implementation of eventsourcing on top of Actor model - Akka Persistance, PersistentFSM. I don't say it is better but it is not bad to know other attempts. Actors can use side-effect but if I remember correctly sometimes it requires to write explicit code how to replay.

Maybe replay feature might be skewing our decisions where code belongs. It seems that we are starting to think as ... "I want this replayable hence it has to go to a reducer". It can lead to a code with unclear boundaries because we are not slicing it by its responsibilities/roles and we tend to artificially slice our business logic to make it replayable.

If I would put replay feature aside how I would write my business code? I would aim to have my business logic in front of an action log and have it in one place. Business logic state machines, or whatever I would be using, would do all the hard complex decisions resulting in an already resolved stream of FACTS (actions). Reducers would be generally simple updaters and their main responsibility would be how to apply action payload to Redux.state. They could have some logic but in a sense to interpret facts differently to get another kind of view on fact (action) stream to present it to a user.

To slice responsibilities I like to think about it this way as well. What code could be potentially requested to be re-implemented on a server? F.e. to support slow devices better by moving computation expensive business logic, maybe some security reasons, intelligence property hiding whatever. I can't move logic from reducers to server that easily because it is bound to a view layer. If complex business logic is written in front of the action log I can replace it by REST calls translating to the same action stream and my view layer should work (probably).

The downside is that unlike in Akka Persistance I have no facility how to replay this code.

I have to take a deeper look on Elm one day and understand how your proposal would work.

@gaearon Thanks for menioning Elm. Found this conversation - https://gist.github.com/evancz/44c90ac34f46c63a27ae - with similar ideas (but much more advanced).

They introduce concept of Tasks (http, db, etc) and Effects (just a queue of tasks). So your "reducer" can indeed return new state and effects.

Cool thing about it (and I hadn't think about it this way) - is that if you can collect tasks while chaining your reducers - and later apply some function to this list. Say you could batch http requests, wrap DB queries with transactions, etc.

Or you could have, say "replay manager" which is just a noop for effects.

@merk I think most has been already written. Just that this kind of autonomous side-effecting code is probably the most tricky. It will probably wreck havoc on replay because intervals won't be synchronized with timetravel assuming that timetravel is running on the same code and intervals will start in replay mode as well.

Usual application doesn't need token and expiration countdown presented to a user so technically it doesn't have to be in Redux.state. Admin app might need to have it. Hence you can implement it both ways whatever fits you.

One option is to start expiration countdown in login AC and I assume it runs endlessly and dies with the app or logoff has to clean it. When interval triggers it does what is needed to confirm token and if expired it dispatches LOGIN_EXPIRED action and listening reducer clears user session and changes location which in turn triggers router transition to /login.

Another is to use 3rd party lib and delegate concerns to it and translate its api to LOGIN_EXPIRED action. Or write your own and keep token and countdown state here if you don't need it in view layer.

You can keep it in Redux.state but this state is volatile. With large state we can get to same problems as programming with global variables. And it is hard, probably impossible to test it. It is easy to test reducers but then one proves it works when only one reducer is updating that particular key in state object. This can get bad in large project with many developers of different qualities. Anybody can decide to write reducer and think that some piece of state is good to update and I can't imagine how to test all reducers together in all possible update sequences.

Depending on your use case it might make sense to protect this state, because it is login - quite important functionality, and have it separated. If this state is needed in a view layer then replicate it to Redux.state in a similar fashion like react-router location state is replicated to Redux.state in some examples around. If your state and team is small and well behaved you can be fine to have it in one place.

@vladar @vladap

Wow, that Elm gist is freaking cool! It also reminds me of Cycle.js “drivers” architecture.

@merk Actually, autonomous LOGIN_EXPIRED wouldn't affect replay much because it would get in the end of the action log and wouldn't be processed immediately by a replay and maybe it would not get to action log at all - I don't know how exactly replay is implemented.

@gaeron It seems that at the high level Elm and Cycle implements the pattern I was trying to describe if I understand it correctly. I have a browser which gives me a base platform, I extend this platform with my service layer (http, db...) to build a platform for my app. Then I need some kind of a glue which interacts with the service layer and allows my pure app to indirectly communicate with it so that my app can describe what it wants to do but not actually execute it itself (messages sent to Cycle drivers, Elm Effects having a list of tasks). I could say my app is building a "program" using data structures (reminds me about code as data mantra) which is then passed to service layer for execution and my app is only interested in a result of this "program" which it then applies to its state.

@merk: Further. If token and its expiration would be placed in Redux.state/or anywhere in any other js service, it doesn't transfer when user opens app in a new tab. I would say user expects he stays logged. So I assume this state should be actually in SessionStore.

Even without that, states can become separated when token expiration doesn't mean immediate clean up of user info and transition to /login. Till user doesn't touch server with his interactions (he has enough data cached) he can continue to work and LOGIN_EXPIRED action triggers only once he does something requiring a server. isLogged state based on Redux.state doesn't have to mean that valid token exists. isLogged state in Redux.state is used to decide what to render, state around a token can be hidden to a view layer and maintained solely on action creators level without having to write actions and reducers for it, except those affecting a view layer.

@vladar I think I might get your generall point. I think I haven't realized it before.

There is no easy way how to return control from reducer back to action creator and passing some values. Let's assume these values are not needed for rendering, they are even temporary but they are needed to initiate an async operation.

actionCreator() {
  ... getState
  ... some logic L1
  ... dispatch({ACTION1})
}

reducer() {
  case ACTION1
    ... some logic L2
    ... x, y result from L2
    ... some logic L3
    ... resulting in new state
    ... return new state
}

There are 3 options how to initiate async operation using x, y computed in reducer. None of them is perfect.

1) Move logic from reducer to action creator and decrease power of hot-reload. Reducer is only dumb state updater or have logic L3 onwards.

2) Save x, y to Redux.state, polluting it with temporary/transient values and basically using it as a global communication channel between reducers and action creators which I'm not convinced I like. I think it is fine if it is actual state but not for these kind of values. Action creator changes to:

actionCreator() {
  ... getState
  ... some logic L1
  ... dispatch({ACTION1})
  // I assume I get updated state if previous dispatch is sync.
  // If previous dispatch is async it has to be chained properly.
  // If updated state can't be received here after a dispatch
  // then I would think there is a flaw.
  ... getState
  ... asyncOperation(state.x, state.y)
}

3) Save x, y to state, use it as props in some component and tunnel it through the whole view layer loop using componentWillReceiveProps triggering new action creator and the async operation. The worst option if you ask me, spreading business logic all over the place.

@vladar
Generally it doesn't matter where async is initiated as far as the result of this operation is packed as an action, dispatched and applied by a reducer. It will work the same. It is the same discussion as with vanilla flux if async should be initiated in action creators or in stores.

It would require that Redux is able to identify and ignore these async calls on replay which is exactly what you suggest.

There is no easy way how to return control from reducer back to action creator and passing some values.

I would say this is one of the symptoms. Your second example illustrates my point of view very well (see below).

Consider generalized flow of state transition (in FSM world):

  1. Event arrives
  2. Given current state FSM calculates new target state
  3. FSM performs operations to transition from current state to new state

Note that current state is important! Same event can lead to different outcomes depending on current state. And as a result to different sequences of operations (or arguments) at step 3.

So this is pretty much how state transition in Flux works when your action is sync. Your reducers/stores are indeed FSMs.

But what about async actions? Most of Flux examples with async actions force you to think that it is inverted:

  1. AC performs async operation (regardless of current state)
  2. AC dispatches action as a result of this operation
  3. Reducer/Store handles it and changes state

So current state is ignored, target state is assumed to always be the same. While in reality consistent state transition flow is still the same. And it has to look like this (with ACs):

  1. AC gets current state before action is dispatched
  2. AC dispatches action
  3. AC reads new state after action
  4. Given state before action and after - it decides which operations to perform (or which values to pass as arguments)

Exactly your second example. I am not saying, that all of these steps are required all the time. Often you can omit some of them. But in complex cases - that's how you'll have to act. It is generalized flow of any state transition. And it also means that boundaries of your FSM have moved to AC vs reducer/store.

But moving boundaries causes other problems:

  1. If async operations were in store / reducer - you could have two independent FSMs reacting to same event. So to add new feature - you could just add store / reducer that reacts to some existing event and that's all.
    But with operations in AC - you'd have to add store/reducer and also go and edit existing AC by adding async part of logic there as well. If it already contained async logic for some other reducer... it gets not cool.
    Maintaining such code is obviously harder.
  2. Control flow of your app is different in case of sync vs async actions. For sync actions reducer is in control of transition, in case of async - AC is effectively in control of all transitions that could be potentially caused by this event/action.

Note, that most of problems are hidden by simple state requirements of most of web apps. But if you have complex stateful app - you will definately face with these situations.

In most cases you will probably come up with all sorts of workarounds. But you could avoid them in the first place if your state transition logic was better encapsulated and your FSMs were not separated between ACs and reducers.

Unfortunately "side-effecting" part of state transition is still a part of state transition, and not some separate independent piece of logic. That's why for me (state, action) => (state, sideEffects) looks more natural. Yeah, that's not a "reduce" signature anymore %) But the domain of framework is not data transformations, but state transitions.

It is the same discussion as with vanilla flux if async should be initiated in action creators or in stores.

Yes, but Flux doesn't forbid you to have async stuff in stores as long as you dispatch() in async callbacks vs mutating state directly. They are kind of un-opinionated even if most implementations recommed using ACs for async. Personally I think it is a reminiscence of MVC, because it is convenient mentally to treat ACs as controllers vs making mental shift to FSMs.

@vladar

(state, action) => (state, sideEffects)

Just thinking. This means we would have to change return type of combineReducers function into [state, SideEffects] or [state, [SideEffects]] and change its code to combine SideEffects from reducers. Original (state, action) => state reducer type could stay supported because combineReducers would coerce (state) return type to [state] or [state, []].

Then we need to execute and dispatch SideEffects somewhere in Redux. It could be done in Store.dispatch() which would apply new state and execute SideEffects list and dispatch it. I'm thinking if we can get into some nice endless recursion.

Alternatively it could be executed somewhere on the middleware level but then I think Store.dispatch would need to return SideEffects list together with an action.

@vladar

Yes, but Flux doesn't forbid you to have async stuff in stores as long as you dispatch() in async callbacks vs mutating state directly. They are kind of un-opinionated even if most implementations recommed using ACs for async. Personally I think it is a reminiscence of MVC, because it is convenient mentally to treat ACs as controllers vs making mental shift to FSMs.

I agree that treating ActionCreators as controllers isn't a good mindset. It is the route cause why I end-up thinking that for a complex logic I need to return control from reducer back to AC. I shouldn't need that. If AC handle async, they should be at most like request handlers - execute based on decision made somewhere else and this request shouldn't cross-cut domains.

To avoid their role as controllers the only option now is to route logic through smart components and use ActionCreators only to execute the initial step of a logic in a direct response to UI. The actual first decision is made in a smart component based on Redux.state and/or component's local state.

This approach f.e. in your previous example of deleting category and refreshing/deleting something else in response using webapi wouldn't be nice. I assume it will lead to a mixture of different techniques based on dev preferences - should it be in AC, in reducer, in smart component? Let see how patterns will evolve.

I was buying the point to keep async calls away from stores. But I don't think it is valid because, imho, it significantly rises complexity somewhere else. On the other hand I don't see much complexity having async function in a store/reducer as far as it only execute there and dispatch - or pure approach and handle these functions as data and trigger them somewhere far away.

Actors do similarly, they can execute async and send message (action object) to other actor, or send it to yourself to continue in their FSM.

I'm closing because there doesn't seem to be anything actionable in here. Async Actions doc should answer most questions. Further theoretical discussions don't seem valuable to me without the actual code we could run. ;-)

@gaearon can you add actual code implementing the right way of handling API calls to one of the examples with redux (outside of the "real-world" example which uses middleware)?

I still have questions about actual placement after reading all the docs and this issue a few times I'm having trouble understanding which files to place these side effects https://github.com/rackt/redux/issues/291#issuecomment-123010379. I think this is the "correct" example from this issue thread.

Thanks in advance for any clarification in this area.

I'd like to point out an important thought about impure action creators:

Putting async calls in the action creators, or having impure action creators in general, has one big disadvantage to me: it fragments the control logic in such a way that completely swapping out your application's control logic is not as simple as just swapping out your store creator. I intend to always use middleware for async processes and avoid impure action creators completely.

The app I'm developing will have at least two versions: one that runs in an onsite Raspberry Pi, and another that runs a portal. There may even be separate versions for a public portal/portal operated by a customer. Whether I have to use different APIs is uncertain, but I want to prepare for that possibility as best I can, and middleware allows me to do that.

And to me the concept of having API calls distributed among action creators runs completely counter to the Redux concept of centralized control over state updates. Sure, the mere fact that an API request is running in the background is not part of the Redux store's state -- but it's still application state nonetheless, something you want to have precise control over. And I find that I can have the most precise control over it when, like with Redux stores/reducers, that control is centralized rather than distributed.

@jedwards1211 You might be interested in https://github.com/redux-effects/redux-effects in case you haven't checked them out yet, as well as in discussion over at #569.

@gaearon cool, thanks for pointing that out! In any case, using my own middleware hasn't been too difficult so far :)

I created an Elm-like approach: redux-side-effect. The README explains the approach and compares it to alternatives.

@gregwebs I like redux-side-effect, though it would be awkward with swappable control logic also, because there's the question of how I get the sideEffect function into my reducer modules, when there are multiple different store configurer modules to be used in different builds.

I could use a funny hack though: just store sideEffect in the state itself, so it's automatically available to the reducer! :stuck_out_tongue_winking_eye:

Something like https://github.com/rackt/redux/pull/569/ is close to ideal for how I want to work, though of course I don't want to use it in a production project unless it becomes a standard part of the API.

Here's an idea: have a middleware stick a sideEffect function on the action. Slightly hacky, but the tiniest possible change necessary to make reducers able to initiate async code:

sideEffectMiddleware.js:

export default store => next => action => {
  let sideEffects = [];
  action.sideEffect = callback => sideEffects.push(callback);
  let result = next(action);
  sideEffects.forEach(sideEffect => sideEffect(store));
  return result;
};

There are multiple ways to the simple sideEffect approach. Please try out your variants and report back

I refactored my code to use the sideEffectMiddleware approach above, and I really like the resulting code organization. It's nice not to have to import the sideEffect function from anywhere into my reducers, it just comes in the action.

@jedwards1211 I published your code as actionSideEffectMiddleware in version 2.1.0 of the package.

@gregwebs cool! Mind listing me as a collaborator?

@jedwards1211 you are added. Maybe you will add the proper replay support :) I cannot take advantage of that yet where I am using this.

@gaearon I take it that recorded and replayed actions don't go through the middleware at all, right?

They do, it's just that they're already plain objects by the time they are recorded, so middleware usually doesn't intervene.

@gaearon Hmmm. So I haven't actually tried out that feature, but...I'm guessing some side effect middlewares would break record/replay as well? Take redux-effects-fetch for instance, it would still perform an ajax request on a plain object FETCH action:

function fetchMiddleware ({dispatch, getState}) {
  return next => action =>
    action.type === 'FETCH'
      ? fetch(action.payload.url, action.payload.params).then(checkStatus).then(deserialize, deserialize)
      : next(action)
}

If the recorder cleans the [success, failure] callbacks out of the FETCH action's steps, then the redux-effects middleware would not dispatch any state-altering actions, but still, one wouldn't want replaying to trigger a bunch of ajax requests.

Is there some way to separate the "pure" middleware (that never dispatches additional actions) like redux-logger from "impure" middleware (that may dispatch actions)?

Unfortunately I haven't looked into redux-effects closely so I can't answer your question.

No problem, in any case does the recorder add any flags to the actions that middleware can use to decide whether or not to perform side effects? I'm just trying to figure out a way in my own middleware to be able to support the devtools correctly

It doesn't. It is expected that devTools() is put _after_ applyMiddleware in enhancer composition chain, so that by the time the action is reached, it is a “final” action that does not need further interpretation.

Oh, I see, so then after a middleware performs a side effect it could just remove any fields from the action/tweak it so that it wouldn't trigger the side effect when it goes back through the middleware upon replay, and then it should work with the dev tools, right?

Yeah, that seems like a good way.

Great, thanks for enlightening me!

@gaearon @vladar @jedwards1211 You might be interested in https://github.com/salsita/redux-side-effects. Basically it is an implementation of elmish (state, action) => (state, sideEffects) but instead of reducer returning tuple (which you already noticed is not possible) it is yield-ing effects.
I played with it shortly, hot reload and replay seems to be ok. I don't see any redux feature broken, so far, but maybe some of the senior reduxers might find something. :)
For me, this looks as really important addition to redux stack as it allows logic to be primarily in reducers making them an effective and testable state machines, who delegate effects (transition actions whose result does not depend only on current state) to external services (pretty much like Elm architecure, effects and services).

Another approach is redux-saga which also uses generators but keeps side effects separate from the reducers.

@gaearon I believe @minedeljkovic meant the

For me, this looks as really important addition to redux stack as it allows logic to be primarily in reducers making them an effective and testable state machines, who delegate effects (transition actions whose result does not depend only on current state) to external services (pretty much like Elm architecure, effects and services).

as the main advantage over traditional approach, because https://github.com/yelouafi/redux-saga/ is very similar how redux-thunk works, instead of some syntax sugar on top of it which makes long running transactions easier.

https://github.com/salsita/redux-side-effects on the other hand is more like Elm architecture.

Yes, redux-saga and redux-side-effects use generators only to declare side effects, keeping sagas and reducers pure, respectively. That is similar.

Two reasons why I prefer that in reducers:

  1. You have explicit access to current state which may affect how effect should be declared (I think that was one of the @vladar 's points during this discussion)
  2. There is no new concept introduced (Saga in redux-saga)

My comment in https://github.com/rackt/redux/issues/1139#issuecomment-165419770 is still valid as redux-saga won't solve this and there is no other way to solve this except accepting the model used in Elm architecture.

I put a simple gist that is trying to emphasize my points about redux-side-effects: https://gist.github.com/minedeljkovic/7347c0b528110889aa50

I tried to define simplest possible scenario that I think is just "real world" enough but still emphasize some of the points from this discussion.

Scenario is:
Application has "User registration" part where personal user data should be entered. Amongst other personal data, country of birth is selected from list of countries. Selection is performed in "Country selection" dialog, where user selects a country and have a choice of confirm or cancel a selection. If user is trying to confirm selection, but no country is selected, she should be warned.

Architecture constraints:

  1. CountrySelection functionality should be modular (in the spirit of redux ducks), so it can be reused in multiple parts of application (e.g. also in "Product administration" part of application, where country of production should be entered)
  2. CountrySelection's slice of state should not be considered global (to be in the root of redux state), but it should be local to module (and controlled by that module) that is invoking it.
  3. Core logic of this functionality needs to include as little moving parts as possible (In my gist this core logic is implemented in reducers only (and only the most important part of the logic). Components should be trivial, as all redux-driven components should be :) . Their only responsibility would be rendering current state, and dispatching actions.)

Most important part of gist, regarding this conversation, is in the way countrySelection reducer is handling CONFIRM_SELECTION action.

@gaearon, I would mostly appriciate your opinion on my statement that redux-side-effects as addition to standard redux provide surface for simplest solution considering constratints.

Possible alternative idea for implementation of this scenario (without using redux-side-effects, but using redux-saga, redux-thunk or some other method), would be also very appriciated.

@tomkis1, I would love to here your opinion, am I using or abusing your library here. :)

(There is slightly different implementation in this gist https://gist.github.com/minedeljkovic/9d4495c7ac5203def688, where globalActions is avoided. It is not important for this topic, but maybe someone would like to comment on this pattern)

@minedeljkovic Thanks for the gist. I see one conceptual problem with your example. redux-side-effects is meant to be used for side effects only. In your example however there are no side-effects but rather long living business transaction and therefore redux-saga is much more suitable. @slorber and @yelouafi can shed more light on this.

In other words the most concerning issue for me is synchronous dispatching of new action within reducer (https://github.com/salsita/redux-side-effects/pull/9#issuecomment-165475991):

yield dispatch => dispatch({type: COUNTRY_SELECTION_SUCCESS, payload: state.selectedCountryId});

I believe @slorber came with the term called "business side effect" and this is exactly your case. redux-saga shines in solving this particular problem.

@mindjuice i'm not perfectly sure to understand your example but I like the onboarding example I gave here: https://github.com/paldepind/functional-frontend-architecture/issues/20#issuecomment-162822909

The saga pattern also permits to make some implicit things explicit.
For example you just describe what happened by firing actions (i prefer events because for me they should be in the past tense), and then some business rules kicks in and update the UI. In your case, the display of the error is implicit. With a saga, on confirm, if no country is selected, you would probably dispatch an action "NO_COUNTRY_SELECTED_ERROR_DISPLAYED" or something like that. I prefer that to be totally explicit.

Also you could see the Saga as a coupling point between ducks.
For example you have duck1 and duck2, with local actions on each. If you don't like the idea to couple the 2 ducks (I mean one duck would be using the actionsCreators of the second duck) you could just let them describe what happened, and then create a Saga that wires some complex business rules between the 2 ducks.

Sooooo, it's an insainely long thread and still is there any solution to the problem yet?

Suppose, you have an asynchronous action() and your code must either indicate an error or show the result.

The first approach was to make it like

// the action call
action().then(dispatch(SUCCESS)).catch(dispatch(FAILURE))

// the reducer
case SUCCESS:
    state.succeeded = true
    alert('Success')

case FAILURE:
    state.succeeded = false
    alert('Failure')

But it turns out that it's not Redux-way because now the reducer contains "side effects" (I don't know what that's supposed to mean).
Long story short, the Right way is to move these alert()s out from the reducer somewhere.

That somewhere could be the React component which calls this action.
So now my code looks like:

// the reducer
case SUCCESS:
    state.succeeded = true

case FAILURE:
    state.succeeded = false

// the React component
on_click: function()
{
    action().then
    ({
        dispatch(SUCCESS)

        alert('Success')
        // do something else. maybe call another action
    })
    .catch
    ({
        dispatch(FAILURE)

        alert('Failure')
        // do something else. maybe call another action
    })
}

Is this now the Right way to do it?
Or do I need to investigate more and apply some 3rd party library?

Redux is not simple at all. It's not easy to understand in case of real-world apps. Mabye a sample real-world app repo is needed as a canonical example illustrating the Right way.

@halt-hammerzeit Redux itself it very simple; the confusing fragmentation comes from different needs or opinions about integrating/separating side effects from reducers when using Redux.

Also, the fragmentation comes from the fact that it's really not that hard to do side effects however you want with Redux. It only took me about 10 lines of code to roll my own basic side effect middleware. So embrace your freedom and don't worry about the "right" way.

@jedwards1211 "Redux itself" doesn't have any value or meaning and doesn't make any sense because its main purpose is to solve the everyday issues of Flux developers. That implies AJAX, beautiful animations and all the other _ordinary and expected_ stuff

@halt-hammerzeit you have a point there. Redux certainly seems intended to get most people to agree on how to manage state updates, so it's a shame that it hasn't gotten most people to agree on how to perform side effects.

Have you seen the "examples" folder in the repo?

While there are multiple ways to perform side effects, generally the recommendation is to either do that outside Redux or inside action creators, like we do in every example.

Yes, I know...all I'm saying is, this thread has illustrated a lack of consensus (at least among people here) about the best way to perform side effects.

Though perhaps most users do use thunk action creators and we're just outliers.

^ what he said.

I'd prefer all the greatest minds to agree on a single Righteous solution
and carve it in stone so that i don't have to read through a 9000 screens
thread

On Thursday, January 7, 2016, Andy Edwards [email protected] wrote:

Yes, I know...all I'm saying is, this thread has illustrated a lack of
consensus (at least among people here) about the best way to perform side
effects.


Reply to this email directly or view it on GitHub
https://github.com/rackt/redux/issues/291#issuecomment-169751432.

So, i guess, the currently agreed dolution is to do rverything in action
creators. I,m gonna try using this approach and incase i find any flaws in
it i'll post back here

On Thursday, January 7, 2016, Николай Кучумов [email protected] wrote:

^ what he said.

I'd prefer all the greatest minds to agree on a single Righteous solution
and carve it in stone so that i don't have to read through a 9000 screens
thread

On Thursday, January 7, 2016, Andy Edwards <[email protected]

Yes, I know...all I'm saying is, this thread has illustrated a lack of
consensus (at least among people here) about the best way to perform side
effects.


Reply to this email directly or view it on GitHub
https://github.com/rackt/redux/issues/291#issuecomment-169751432.

This and similar threads exist because 1% of Redux users like to look for more powerful / pure / declarative solutions for side effects. Please don't take the impression that nobody can agree on that. The silent majority uses thinks and promises, and are mostly happy with this approach. But like any tech it has downsides and tradeoffs. If you have complex asynchronous logic you might want to drop Redux and explore Rx instead. Redux pattern is easily expressive in Rx.

Ok, thanks

On Thursday, January 7, 2016, Dan Abramov [email protected] wrote:

This and similar threads exist because 1% of Redux users like to look for
more powerful / pure / declarative solutions for side effects. Please don't
take the impression that nobody can agree on that. The silent majority uses
thinks and promises, and are mostly happy with this approach. But like any
tech it has downsides and tradeoffs. If you have complex asynchronous logic
you might want to drop Redux and explore Rx instead. Redux pattern is
easily expressive in Rx.


Reply to this email directly or view it on GitHub
https://github.com/rackt/redux/issues/291#issuecomment-169761410.

@gaearon yeah, you're right. And I appreciate that Redux is flexible enough to accommodate all of our different approaches!

@halt-hammerzeit take a look at my answer here where I explain why redux-saga can be better than redux-thunk: http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34599594

@gaearon By the way, your answer on stackoverflow has the same flaw as the documentation - it covers just the simplest case
http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34599594#34599594
Real-world applications quickly go beyond just fetching data via AJAX: they should also handle errors and perform some actions depending on the output of the action call.
Your advice is to just dispatch some other event and that's it.
So what if my code wants to alert() a user on action completion?
That's where those thunks won't help, but promises will.
I currently write my code using simple promises and it works that way.

I have read the adjacent comment about redux-saga.
Didn't understand what it does though, lol.
I'm not so much into the monads thing and such, and I still don't know what a thunk is, and I don't like that weird word to begin with.

Ok, so I keep using Promises in React components then.

We suggest returning promises from thunks throughout the docs.

@gaearon Yeah, that's what i'm talking about: on_click() { dispatch(load_stuff()).then(show_modal('done')) }
That will do.

I think you're still missing something.
Please see README of redux-thunk.
It shows how to chain thunk action creators under the "Composition" section.

@gaearon Yeah, that's exactly what I'm doing:

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

It's exactly what I wrote above:

on_click()
{
    dispatch(load_stuff())
        .then(() => show_modal('done'))
        .catch(() => show_error('not done'))
}

That README section is well written, thx

No, this wasn't my point. Take a look at makeSandwichesForEverybody. It's a thunk action creator calling other thunk action creators. This is why you don't need to put everything in components. See also async example in this repo for more of this.

@gaearon But I think it wouldn't be appropriate to put, say, my fancy animation code into the action creator, would it?
Consider this example:

button_on_click()
{
    this.props.dispatch(load_stuff())
        .then(() =>
        {
                const modal = ReactDOM.getDOMNode(this.refs.modal)
                jQuery(modal).fancy_animation(1200 /* ms */)
                setTimeout(() => jQuery.animate(modal, { background: 'red' }), 1500 /* ms */)
        })
        .catch(() =>
        {
                alert('Failed to bypass the root mainframe protocol to override the function call on the hyperthread's secondary firewall')
        })
}

How would you rewrite it the Right way?

@halt-hammerzeit you can make the actionCreator return the promises so that the component can show some spinner or whatever by using local component state (hard to avoid when using jquery anyway)

Otherwise you can manage complex timers to drive animations with redux-saga.

Take a look at this blog post: http://jaysoo.ca/2016/01/03/managing-processes-in-redux-using-sagas/

Oh, in this case it's fine to keep it in component.
(Note: generally in React it's best to use declarative model for everything rather than showing modals imperatively with jQuery. But no big deal.)

@slorber Yeah, I'm already returning Promises and stuff from my "thunks" (or whatever you call them; errr, i don't like this weird word), so I can handle spinners and such.

@gaearon Ok, I got you. In the ideal machinery world it would of course be possible to stay inside the declarative programming model, but reality has its own demands, irrational, say, from the view of a machine. People are irrational beings not just operating with pure zeros and ones and it requires compromising the code beauty and purity in favour of being able to do some irrational stuff.

I'm satisfied with the support in this thread. My doubts seem to be resolved now.

@gaearon very awesome explanation! :trophy: Thanks :+1:

Was this page helpful?
0 / 5 - 0 ratings