Home

Awesome

redux-effex

I like using async/await, and while I found that redux-saga solves pretty much any problem it sets out to, I don't have most of those problems, and I don't want to force other developers working on my codebases to suddenly become experts with generators and the fairly large redux-saga API.

Example

Store.js

/**
 * @flow
 */

import { applyMiddleware, combineReducers, createStore } from 'redux';
import { effectsMiddleware } from 'redux-effex';

import ApiStateReducer from 'ApiStateReducer';
import CurrentUserReducer from 'CurrentUserReducer';
import HistoryReducer from 'HistoryReducer';
import Effects from 'Effects';

export default createStore(
  combineReducers({
    currentUser: CurrentUserReducer,
    history: HistoryReducer,
    apiState: ApiStateReducer,
  }),
  applyMiddleware(effectsMiddleware(Effects)),
);

Effects.js

/**
 * @flow
 */

import ActionTypes from 'ActionTypes';
import type { EffectErrorHandlerParams } from 'redux-effex';

import openAppAsync from 'openAppAsync';
import signInAsync from 'signInAsync';
import signOutAsync from 'signOutAsync';

function genericErrorHandler({action, error}: EffectErrorHandlerParams) {
  console.log({error, action});
}

export default [
  {action: ActionTypes.OPEN_APP, effect: openAppAsync, error: genericErrorHandler},
  {action: ActionTypes.SIGN_OUT, effect: signOutAsync, error: genericErrorHandler},
  {action: ActionTypes.SIGN_IN, effect: signInAsync, error: genericErrorHandler},
];

openAppAsync.js

/**
 * @flow
 */

import { Alert, Linking } from 'react-native';
import type { EffectParams } from 'redux-effex';

import AppDataApi from 'AppDataApi';
import Actions from 'Actions';
import ActionTypes from 'ActionTypes';
import LocalStorage from 'LocalStorage';

export default async function openAppAsync({action, dispatch, getState}: EffectParams) {
  let { app } = action;

  if (typeof app === 'string') {
    app = await fetchAppDataAsync(app, dispatch);
  }

  dispatch(Actions.addAppToHistory(app));
  const { history } = getState();
  LocalStorage.saveHistoryAsync(history);

  AppDataApi.incrementViewCountAsync(app.url_token);
  Linking.openURL(`exp://rnplay.org/apps/${app.url_token}`);
}

async function fetchAppDataAsync(url, dispatch) {
  let app;

  try {
    dispatch(Actions.showGlobalLoading());
    let httpUrl = url.indexOf('rnplay:') === 0 ? url.replace('rnplay:', 'http:') : url;
    app = await AppDataApi.fetchAppDataAsync(httpUrl);
  } catch(e) {
    Alert.alert(
      'Error',
      `There was a problem loading ${url}.`,
      [
        {text: 'Try again', onPress: () => dispatch(Actions.openApp(url))},
        {text: 'Cancel', style: 'cancel'},
      ]
    );
    throw e;
  } finally {
    dispatch(Actions.hideGlobalLoading());
  }

  return app;
}

signInAsync.js

/**
 * @flow
 */

import type { EffectParams } from 'redux-effex';

import Actions from 'Actions';
import LocalStorage from 'LocalStorage';

export default async function signInAsync({action, dispatch}: EffectParams) {
  let { user } = action;

  await LocalStorage.saveUserAsync(user);
  dispatch(Actions.setCurrentUser(user));
}

signOutAsync.js

/**
 * @flow
 */

import type { EffectParams } from 'redux-effex';

import Actions from 'Actions';
import LocalStorage from 'LocalStorage';

export default async function signOutAsync({action, dispatch}: EffectParams) {
  await LocalStorage.clearAll();
  dispatch(Actions.setCurrentUser(null));
}

A contrived example of waiting for another action

waitForAllStepsAsync.js

/**
 * @flow
 */

import type { EffectParams } from 'redux-effex';

import ActionTypes from 'ActionsTypes'

export default async function waitForAllStepsAsync({nextDispatchAsync}: EffectParams) {
  let action1 = await nextDispatchAsync(ActionTypes.STEP_ONE);
  console.log(`step one complete: ${action1.payload}`);

  let action2 = await nextDispatchAsync(ActionTypes.STEP_TWO);
  console.log(`step two complete: ${action2.payload}`);

  let action3 = await nextDispatchAsync(ActionTypes.STEP_THREE);
  console.log(`step three complete: ${action3.payload}`);

  alert('success!');
}

Naming

The suffix is ex instead of ects because effects is surely taken and I work on Expo, whose name begins with ex. Cool story.

License

MIT