React native redux thunk store selector and api

This will be an example of react native app. An example of fetching data from an api, the data will be stored in global state, and managed via redux, thunk, and selector.

Installing dependencies.

yarn add axios
yarn add @react-native-community/async-storage
yarn add redux
yarn add react-redux
yarn add redux-axios-middleware
yarn add redux-persist
yarn add redux-thunk
yarn add reselect

Account action accountAction.js, the action to be dispatched for sending account data.

export const FETCH_ACCOUNT_SUCCESS = 'FETCH_ACCOUNT_SUCCESS';

export function fetchAccountSuccess(account) {
    console.log(`${Date()} >>> fetchAccountSuccess account: ` + JSON.stringify(account));
    return {
        type: FETCH_ACCOUNT_SUCCESS,
        payload: account
    }
}

Account reducer accountReducer.js, the reducer for receiving the dispatched account action, massages the data and return the finalized account data.

import { FETCH_ACCOUNT_SUCCESS } from '../actions/accountAction';
import { API_ERROR } from '../actions/errorAction';
import { API_LOADING } from '../actions/loadingAction';

const accountInitialState = {
    loading: false,
    account: {},
    error: null
};

function selectInterestedAccountInfo(account) {
    return Object.keys(account).reduce(function(obj, k) {
        if (["login", "url", "name", "created_at", "bio", "email", "public_repos"].includes(k)) {
            obj[k] = account[k];
        }
        return obj;
      }, {});
}

export function account(state = accountInitialState, action) {
    switch(action.type) {
        case API_LOADING:
            return {
                ...state,
                loading: true
            }
        case FETCH_ACCOUNT_SUCCESS:
            console.log("account state: before >>>>>>>>>>", state);
            const payloadAccount = selectInterestedAccountInfo(action.payload);
            state.account[payloadAccount.login] = payloadAccount;
            console.log("account state: after >>>>>>>>>>", Object.keys(state.account).length);
            return {
                ...state,
                loading: false
            }
        case API_ERROR:
            return {
                ...state,
                loading: false,
                error: action.error
            }
        default:
            return state;
    }
}

Account selector accountSelector.js, the selector for caching the account data.

import { createSelector } from 'reselect';

export const getAccountSuccess = state => state.account.account;
export const getAccountLoading = state => state.account.loading;
export const getAccountError = state => state.account.error;

function _accountState(loading, acccount, error) {
    return {
        loading, acccount, error
    }
}

export const accountState = createSelector(
    (state) => state.account.loading,
    (state) => state.account.account,
    (state) => state.account.error,
    _accountState
);

Account service fetchAccounnt.js, the heavy lifting code where it makes the api call and gets the account data from an api.

import { fetchAccountSuccess } from '../actions/accountAction';
import { apiError } from '../actions/errorAction';
import { apiLoading } from '../actions/loadingAction';

export default function fetchAccount(accountId) {
    return dispatch => {

        dispatch(apiLoading());

        fetch(`https://api.github.com/users/${accountId}`)
        .then(res => res.json())
        .then(res => {
            console.log(`${Date()} >>> fetch account api raw result: ` + JSON.stringify(res));
            if (res.error) {
                throw(res.error);
            }

            dispatch(fetchAccountSuccess(res));

            return res;
        })
        .catch(error => {
            dispatch(apiError(error));
        })
    }
}

Root reducer rootReducer.js, combines all reducers and their data.

import { combineReducers } from 'redux';
import { account } from './accountReducer';
import { availableApi } from './availableApiReducer';

const rootReducer = combineReducers({
  account,
  availableApi,
});

export default rootReducer;

Global state data store.js, gluing the reducer and data, store them in a global store for the app.

import storage from '@react-native-community/async-storage';
import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { persistReducer } from 'redux-persist';
import thunk from 'redux-thunk';
import rootReducer from './reducers/rootReducer';

const persistConfig = {
  key: 'root',
  storage,
};

const middlewares = [thunk];

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = createStore(
  persistedReducer,
  {},
  composeWithDevTools(
    applyMiddleware(...middlewares)
  )
);

Account view GitAccount.js, the view component for displaying account data.

import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getAccountError, getAccountLoading, getAccountSuccess } from '../selectors/accountSelector';
import fetchAccount from '../services/fetchAccount';

// Map the state of the redux store to the component props.
const mapStateToProps = state => {
  console.log(`${Date()} mapStateToProps state: `, state);
  return {
    loading: getAccountLoading(state),
    account: getAccountSuccess(state),
    error: getAccountError(state),
  };
};

// Map the dispatched actions to the component props.
// This makes the function call 'this.props.accountInfo('google')' in componentDidMount possible.
const mapDispatchToProps = dispatch => bindActionCreators({
  fetchAccount: fetchAccount
}, dispatch)

class GitAccount extends Component {
  componentDidMount() {
    console.log(`${Date()} >>> componentDidMount props: `, this.props);
    console.log(`${Date()} >>> componentDidMount state: `, this.state);
    console.log(`${Date()} >>> componentDidMount initiates the api call fetchAccount.`);
    this.props.fetchAccount('google');
  }
  render() {
    console.log(`${Date()} >>> render props: `, this.props);
    console.log(`${Date()} >>> render state: `, this.state);
    const { account } = this.props;
    console.log(`${Date()} >>> render account: `, account);
    return (
      
        {JSON.stringify(account)}
      
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    margin: 16,
  }
});

// currying function https://blog.benestudio.co/currying-in-javascript-es6-540d2ad09400
export default connect(mapStateToProps, mapDispatchToProps)(GitAccount);

Finally the entry point of the app App.js, initialize and put everything together.

import React, { Component } from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { Provider } from 'react-redux';
import { store } from './store';
import GitAccount from './views/GitAccount';
import GitApis from './views/GitApis';

console.log(`${Date()} initialization of Redux for the application.`);

export default class App extends Component {
  render() {
    console.log(`${Date()} Provider is a wrapper to the application and responsible for providing access to the created store`);
    return (
      
        
          
          
        
      
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    marginTop: 50
  }
});

Complete example in Github

Search within Codexpedia

Custom Search

Search the entire web

Custom Search