U Avalos
U Avalos

Reputation: 6798

Complex redux-loop examples?

Are there any examples of using redux-loop to handle complex ajax workflows? The official repo is very minimalistic. From the one example in the wild I was able to find (https://hackernoon.com/why-i-wrote-a-redux-async-outerware-277d450dba74#.2ocaloc58), it seems that redux-loop is very similar to redux-thunk.

Here are some examples of complex ajax workflows:

Upvotes: 3

Views: 1273

Answers (1)

Yiran Sheng
Yiran Sheng

Reputation: 39

I will give a shot at the second workflow (login).

Before going into the code, it's worth noting redux-loop is a lot simpler and offers less than redux-saga in terms of async control flow. But in the spirit of Elm, the focus is on data flow - not surprisingly is typically achieved by data types. Therefore it is helpful to think from the perspective of a statically typed language. In Haskell or Elm, it's probably beneficial to model the problem by data type, which itself encodes a state machine:

data LoginStatus data err =
    LoggedOut       | 
  , LoggedIn data   |
  , LoginError err  | 
  , Pending

Where data and err are type variables represent login data type (tokens) and login errors. JavaScript being dynamically typed, does not have the benefit expressing the same idea - but there are a lot of dynamic tricks one can use to mimic tagged union types like LoginStatus. Without further ago, here's the code:

import {match} from "single-key";

export default function reducer(state, action) {
  return match(state, {
    LoggedOut : () => loggedOutReducer(state, action),
    LoggedIn  : () => loggedInReducer(state, action),
    Pending : () => pendingReducer(state, action),
    LoginError : () => loginErrorReducer(state, action)
  });
} 

Here I will use a simple and lesser known library singe-key to achieve very basic run time union types. A "single-key" object as it's name suggest, is an object with just a key and a value, such as { a: 1 } ("a" is the key, and 1 is the value). We shall model the state with single-key objects - different keys represent different variants of LoginStatus. A few example states:

{
  LoggedOut : true
}


{
  LoggedIn : {
    token : 1235,
    user : { firstName: "John" }
  }
}

{
  Pending : true
}

With that cleared up, here are the sub-reducers used in the main reducer:

// state :: { LoggedIn: {/* some data * } }
function loggedInReducer(state, action) {
  if (action.type === LOGOUT) {
    return {
      LoggedOut : true
    };
  }
  return state;
}
// state :: { Pending : true }
function pendingReducer(state, action) {
  if (action.type === LOGIN_SUCCESS) {
    return {
      LoggedIn : {
        token : action.payload.token,
        user  : action.payload.user
      }
    };
  }
  if (action.type === LOGIN_ERROR) {
    return {
      LoginError : action.payload;  
    };
  }
  if (action.type === LOGOUT) {
    return {
      LoggedOut : true  
    };
  }
  return state;
}
// state :: { LoggedOut : true }
function loggedOutReducer(state, action) {
  if (action.type === LOGIN) {
    return loop({ Pending: true }, Effects.promise(loginRequest));  
  }
  return state;
}
// state :: { LoginError : error }
function loginErrorReducer(state, action) {
  if (action.type === LOGIN) {
    return loop({ Pending: true }, Effects.promise(loginRequest));  
  }
  return { LoggedOut : true }; 
}

These are like transitions in a finite state machine, except sometimes with data attached to the state. Each individual reducers are fairly simple and handles very few action types. Only two reducers return effects:

return loop({ Pending: true }, Effects.promise(loginRequest));

This transitions the state from LoggedOut/LoginError to Pending and specify some side effects - which will be scheduled by redux-loop. You may even merge the two variants into one: { LoggedOut : error | null }, but I feel having a seperate LoginError state is beneficial in the long run.

With some notion of data types, this problem can be easier to reason about than it first appears; you can do the same thing with reducer structured roughly the same and use just redux-thunk.

Upvotes: 2

Related Questions