author

Kien Duong

April 18, 2022

How to add Redux Saga to a React Native project

In order to create a sample for ReactNative – Redux Saga, we setup the project structure like this.

react native redux saga 1

  • src folder contains the source code of the project.
  • components folder contains all components that will be used in the project.
  • containers folder contains the logic of all screens.
  • interfaces folder.
  • store folder is where saga config is stored.

1. Handle the main screen

Install two new packages react-redux & redux-saga. Wrap the app by Provider

    // App.tsx
import React from 'react';
import { ThemeProvider } from '@rneui/themed';
import { store } from './src/store';
import { Provider } from 'react-redux';

// Containers
import Home from './src/containers/Home';

const App = () => {
  return (
    <Provider store={store}>
      <ThemeProvider>
        <Home />
      </ThemeProvider>
    </Provider>
  );
};

export default App;
  

2. Setup Saga

In this sample, we just need to create home reducer to manage states of the home screen. reducers.ts will combine all reducers of all containers in the application. sagas.ts contains root saga of all containers.

    // index.ts
import createSagaMiddleware from 'redux-saga';
import { createStore, applyMiddleware } from 'redux';
import { combinedReducers } from './reducers';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];

export const store = createStore(combinedReducers, applyMiddleware(...middlewares));

sagaMiddleware.run(rootSaga);

  
    // reducers.ts
import { combineReducers } from 'redux';
import homeReducer from '../containers/Home/reducer';

export const combinedReducers = combineReducers({
  home: homeReducer,
});

  
    // sagas.ts
import { all } from 'redux-saga/effects';
import homeSaga from '../containers/Home/saga';

function* rootSaga() {
  yield all([
    homeSaga(),
  ]);
}

export default rootSaga;

  

3. Logic for the home screen

When user clicks to the Increase button, application will dispatch increaseRequest action. homeSaga will listen this action by takeLatest function and call to increase function. Using delay function to wait for some secs (like API response).

When increaseSuccess action is called, reducer will handle to increase 1 to the payload value, after that updating the new value to the store.

Using useSelector function from react-redux to get the value from store and update to view.

photo
    // index.ts
import React from 'react';
import { View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import styles from './styles';

// Components
import TextView from '../../components/TextView';
import ButtonView from '../../components/ButtonView';

// Actions
import { increaseRequest } from './actions';

const Home = () => {
  const dispatch = useDispatch();

  const { loading, number } = useSelector((store: any) => store.home);

  const handleOnPress = () => {
    dispatch(increaseRequest(number));
  };

  return (
    <View style={styles.container}>
      <View style={styles.valueSection}>
        <TextView text={`The number is: ${number}`} />
      </View>

      <ButtonView
        text={'Increase'}
        loading={loading}
        onPress={handleOnPress}
      />
    </View>
  );
};

export default Home;

  
    // actions.ts
import * as types from './types';

export const increaseRequest = (data: number) => ({
  type: types.INCREASE_REQUEST,
  payload: data,
});

export const increaseSuccess = (data: number) => ({
  type: types.INCREASE_REQUEST_SUCCESS,
  payload: data,
});

export const increaseFailed = () => ({
  type: types.INCREASE_REQUEST_FAILED,
});

  
    // types.ts
export const INCREASE_REQUEST = '[Home] Increase number request';
export const INCREASE_REQUEST_SUCCESS = '[Home] Increase number successfully';
export const INCREASE_REQUEST_FAILED = '[Home] Increase number failed';

  
    // reducer.ts
import * as types from './types';
import { IAction } from  '../../interfaces/store';

interface IState {
  loading: boolean;
  number: number;
}

const initialState: IState = {
  loading: false,
  number: 0,
};

const homeReducer = (state = initialState, action: IAction) => {
  switch (action.type) {
    case types.INCREASE_REQUEST: {
      return { ...state, loading: true };
    }
    case types.INCREASE_REQUEST_SUCCESS: {
      return { ...state, number: action.payload + 1, loading: false };
    }
    case types.INCREASE_REQUEST_FAILED: {
      return { ...state, loading: false };
    }
    default: {
      return state;
    }
  }
};

export default homeReducer;

  
    // saga.ts
import { put, takeLatest, delay } from 'redux-saga/effects';
import * as actions from  './actions';

function* increase({ payload }: ReturnType<typeof actions.increaseRequest>) {
  try {
    yield delay(2000);
    yield put(actions.increaseSuccess(payload));
  } catch (e) {
    yield put(actions.increaseFailed());
  }
}

export default function* homeSaga() {
  yield takeLatest(actions.increaseRequest, increase);
}

  
    // styles.ts
import { StyleSheet } from 'react-native';

export default StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  valueSection: {
    marginBottom: 10,
  },
});

  

Recent Blogs