MonkeyBonkey
MonkeyBonkey

Reputation: 47861

How to handle asynchronous promises in a reducer

I'm new to redux so trying to figure out simple example for an authentication use case. The web version of this code gets initial state from localstorage and also sets to localstorage. Converting this example to react-native means that localstorage changes to AsyncStorage which is asynchronous and returns a promise.

How do I handle async initializers in a reducer?

import { AsyncStorage } from 'react-native';
import {
  LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT_SUCCESS,
} from '../actions/login';


const initialState = {
  isFetching: false,
  token: null, // AsyncStorage.getItem('token'),
  profile: null, // AsyncStorage.getItem('profile'),
  isLoggedIn: false,
  errorMessage: null,
};

// The auth reducer. The starting state sets authentication
// based on a token in asyncstorage. In addition we would also null
// the token if we check that it's expired
export default (state = initialState, action) => {
  switch (action.type) {
    case LOGIN_REQUEST:
      return Object.assign({}, state, {
        isFetching: true,
        isLoggedIn: false,
        token: null,
        user: null,
        errorMessage: '',
      });
    case LOGIN_SUCCESS:
      // todo set the async
      // AsyncStorage.multiSet(['token', 'profile'], [action.token, action.profile])
      return Object.assign({}, state, {
        isFetching: false,
        isLoggedIn: true,
        token: action.token,
        user: action.profile,
        errorMessage: '',
      });
    case LOGIN_FAILURE:
      return Object.assign({}, state, {
        isFetching: false,
        isLoggedIn: false,
        token: null,
        errorMessage: action.message,
      });
    case LOGOUT_SUCCESS:
      // AsyncStorage.multiRemove(['token', 'profile'])
      return Object.assign({}, state, {
        isFetching: true,
        isLoggedIn: false,
        token: null,
      });
    default:
      return state;
  }
};

Upvotes: 1

Views: 644

Answers (1)

yadhu
yadhu

Reputation: 15632

Create an action creator for fetching initial data from AsyncStorage. Dispatch the action with key as token or profile when your App loads (you may do that in your root component's componentDidMount).

// create similar actions creators for 'setItem' and 'multiSet' ops
export function loadLocalData(key) {
  return {
    types: [LOAD_LOCAL_DATA, LOAD_LOCAL_DATA_SUCCESS, LOAD_LOCAL_DATA_FAIL]
    asyncStoragePromise: () => AsyncStorage.getItem(key),
    key,
  }
}

now create a middleware for AsyncStorage operations. Make sure you applyMiddleware when you create store.

The most common use case for middleware is to support asynchronous actions without much boilerplate code or a dependency on a library like Rx. It does so by letting you dispatch async actions in addition to normal actions.

export default function asyncStorageMiddleware() {
  return ({ dispatch, getState }) => next => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }

    const { asyncStoragePromise, types, ...rest } = action;

    if (!asyncStoragePromise) {
      return next(action);
    }

    const [REQUEST, SUCCESS, FAILURE] = types;

    next({ ...rest, type: REQUEST });

    const actionPromise = asyncStoragePromise();
    actionPromise
      .then(result => next({ ...rest, result, type: SUCCESS }))
      .catch(error => next({ ...rest, error, type: FAILURE }));

    return actionPromise;
  };
}

finally here is your initialState:

const initialState = {
  isFetching: false,
  token: null,
  profile: null,
  isLoggedIn: false,
  errorMessage: null,
  localLoadErr: '',
};

and reducers:

LOAD_LOCAL_DATA_SUCCESS:
  return {
    ...state,
    [action.key]: action.result,
  };
  break;

LOAD_LOCAL_DATA_FAIL:
  return {
    ...state,
    localLoadErr: action.error,
  };
  break;

Upvotes: 2

Related Questions