Skip to content

Spin off async functions to perform side effects

Notifications You must be signed in to change notification settings

expo/redux-effex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

Spin off async functions to perform side effects

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published