Options
All
  • Public
  • Public/Protected
  • All
Menu

Redux Data FX

Declarative Side Effects for Redux.

There are many options out there to handle side effects with Redux (redux-coco, redux-blabla, redux-gnagna, redux-hiprx...). I didn't quite find what I wanted so I decided I'd made this small store enhancer..

The idea is very simple: in addition of the new state, your reducers can now return a data structure describing the side effects you want to run.

This is very similar to redux-loop, mostly inspired by the elm architecture. But I'd say this implementation's idea comes from re-frame in cljs and its effectful handlers.

(re-frame is an awesome project, you should definitely check it out https://github.com/Day8/re-frame/)

Overview

Usual reducer signature:

(Action, State) -> State

With redux-data-fx, it becomes:

(Action, State) -> State | { newState: State, _fx: Effects }

Your reducer can either return just a new state as usual (same as first signature), or a combination of a new state and another data structure: the description of some side effects that you want to run.

1. Declaratively describe side effects in your reducers.

One of your reducer could now look like this:

function reducer(state = initialState, action) {
  switch(action.type) {
    'noop': 
      return state;

    'fetch/success': 
      return { 
        ...state, 
        data: action.payload.data, 
        isFetching: false 
      };

    'fetch/error': 
      return { 
        ...state, 
        error: 'Oops something wrong happened...',
        isFetching: false 
      };

    'fetch-some-data':
      return {
        newState: { ...state, isFetching: true },
        _fx: {
          fetch: {
            url: 'http://some-api.com/data/1',
            method: 'GET',
            onSuccess: 'fetch/success',
            onError: 'fecth/error',
          },
        }};

    default:
      return state;
  }
}

The 'fetch-some-data' action is what we can call an effectful action, it updates the state and returns a description of the side effects to run as a result.

As you probably have understood already, if we want to run some side effects we return a map with two fields:

{
  newState: 'the new state of your app (what you usually returns from your reducer)',
  _fx: 'a map containing the description of all the side effects you want to perform'
}

IMPORTANT NOTE: if your real app state is a map containing a _fx field and newState field, then that won't work. This is a trade off I am willing to accept for now, since I find this solution really convenient, but we can definitely discuss new approaches (similar to redux-loop for instance).

2. How to run those described side effects ?

In order to actually run these side effects you'll need to register some effects handlers.

This is where the effectful nasty code will run (at the border of the system).

For instance to run our fetch side effects we could register the following handler:

store.registerFX('fetch', (params, getState, dispatch) => {
  fetch(params.url, {
    method: params.method,
    body: params.body,
    ...,
  }).then(res => dispatch({ type: params.onSuccess, payload: res }))
  .catch(res => dispatch({ type: params.onError, payload: res }))
});

The first argument is the handler's id, it needs to be the same as the key used in the _fx map to describe the side effect you want to perfor. In this case 'fetch'.

The second argument is the effect handler, the effectful function that will perform this side effect. It takes 3 parameters:

  • the description of the effect to run (from the _fx object you returned in the reducer)
  • getState useful if you need to access your state here
  • dispatch so you can dispatch new actions from there

3 How to use it?

As simple as this:

npm install --save redux-data-fx

import { reduxDataFX } from 'redux-data-fx'
import someMiddleware from 'some-middleware';

const enhancer = compose(
  applyMiddleware(someMiddleware),
  reduxDataFX
);

const store = createStore(reducer, initialState, enhancer);

// const store = createStore(reducer, initialState, reduxDataFx); if no middleware

// then you can register as many FX as you want
store.registerFX('fetch', (params, getState, dispatch) => {
...
});

store.registerFX('localStorage', (params, getState, dispatch) => {
 ...
});

store.registerFX('dispatchLater', (params, getState, dispatch) => {
 ...
});

Testing

You can keep testing your reducers the same way you were doing before, except that now you can also make sure that effectful actions return the right effects descriptions. Since these descriptions are just data, it's really easy to verify that they are what you expect them to be.

Then you can test your effect handlers independantly, to make sure they run the right side effects given the right inputs.

TODO: Default FX

Create some default effect handlers like: fetch, localStorage, sessionStorage, dispatchLater, dispatch...

Index

External modules

Legend

  • Module
  • Object literal
  • Variable
  • Function
  • Function with type parameter
  • Index signature
  • Type alias
  • Enumeration
  • Enumeration member
  • Property
  • Method
  • Interface
  • Interface with type parameter
  • Constructor
  • Property
  • Method
  • Index signature
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Method
  • Accessor
  • Index signature
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Inherited accessor
  • Protected property
  • Protected method
  • Protected accessor
  • Private property
  • Private method
  • Private accessor
  • Static property
  • Static method

Generated using TypeDoc