genkio/blog

Get started with Redux in React Native

genkio opened this issue · 0 comments

Explained using a expo powered Facebook login implementation.

Install dependencies

$ yarn add redux react-redux redux-thunk

# react-redux to bind react and redux
# redux-thunk to handle async action creators

Create store

// ./store/index.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../reducers';

const store = createStore(
  reducers,
  {}, // default state
  compose(
    applyMiddleware(thunk)
  )
);

export default store;

Create reducers

// ./reducers/index.js
import { combineReducers } from 'redux';
import auth from './auth_reducer';

export default combineReducers({
  auth
});

// ./reducers/auth_reducer.js
import {
  FACEBOOK_LOGIN_SUCCESS,
  FACEBOOK_LOGIN_FAIL
} from '../actions/types';

export default function(state = {}, action) {
  switch (action.type) {
    case FACEBOOK_LOGIN_SUCCESS:
      return { token: action.payload };
    case FACEBOOK_LOGIN_FAIL:
      return { token: null };
    default:
      return state;
  }
}

Create actions

// ./actions/types.js
export const FACEBOOK_LOGIN_SUCCESS = 'facebook_login_success';
export const FACEBOOK_LOGIN_FAIL = 'facebook_login_fail'

// ./actions/auth_actions.js
import { AsyncStorage } from 'react-native';
import { Facebook } from 'expo';

import {
  FACEBOOK_LOGIN_SUCCESS,
  FACEBOOK_LOGIN_FAIL
} from './types';

// action creator
// thunk requires return function(dispatch) { ... }
// refactor with ES6's syntax sugars (automatically return)

export const facebookLogin = () => async dispatch => {
  let token = await AsyncStorage.getItem('fb_token');

  if (token) {
    // Dispatch an action saying FB login is done
    dispatch({ type: FACEBOOK_LOGIN_SUCCESS, payload: token });
  } else {
    // Start up FB Login process
    doFacebookLogin(dispatch);
  }
};

const doFacebookLogin = async dispatch => {
  let { type, token } = await Facebook.logInWithReadPermissionsAsync(`${fb_app_id}`, {
    permissions: ['public_profile']
  });

  if (type === 'cancel') {
    return dispatch({ type: FACEBOOK_LOGIN_FAIL });
  }

  await AsyncStorage.setItem('fb_token', token);
  dispatch({ type: FACEBOOK_LOGIN_SUCCESS, payload: token });
};

// ./actions/index.js
export * from './auth_actions';

main.js setup

// main.js
import { Provider } from 'react-redux';
import store from './store';


// the Provider component accepts a store as prop, it makes the store available to all its children component

class App extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <MainNavigator />
      </Provider>
    );
  }
}

Children component to consume

// AuthScreen.js
import React, { Component } from 'react';
import { View, Text, AsyncStorage } from 'react-native';
import { connect } from 'react-redux';
import * as actions from '../actions';
// or import only the facebookLogin action creator
// import { facebookLogin } from '../actions';

class AuthScreen extends Component {
  componentDidMount() {
    this.props.facebookLogin();
    this.onAuthComplete(this.props);
  }

  componentWillReceiveProps(nextProps) {
    this.onAuthComplete(nextProps);
  }

  onAuthComplete(props) {
    if (props.token) {
      this.props.navigation.navigate('map');
    }
  }

  render() {
    return (
      <View />
    );
  }
}

function mapStateToProps({ auth }) {
  return { token: auth.token };
}

export default connect(mapStateToProps, actions)(AuthScreen);

Lastly, let's hit it home with a diagram I found on the internet, which illustrates the Redux flow.

image