Skip to content
Spin off async functions to perform side effects
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src Fix index.js.flow to include effectsMiddleware Sep 12, 2016
test Transpile for non-flow users, and add tests Sep 8, 2016
.babelrc
.flowconfig Add flow config and export EffectFunction flow type Sep 4, 2016
.gitignore Transpile for non-flow users, and add tests Sep 8, 2016
.npmignore
README.md Update README.md Aug 20, 2018
package.json Fix index.js.flow to include effectsMiddleware Sep 12, 2016
yarn.lock Add yarn lockfile Oct 11, 2016

README.md

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

You can’t perform that action at this time.