React code splitting with redux & redux saga

React code splitting

React code splitting is a technique for optimizing the performance of React applications by splitting the application code into smaller chunks, which can be loaded on demand as needed.

This helps to reduce the initial load time of the application, so this improve its overall performance, and enhance the user experience.

React code splitting can be achieved in a number of ways, depending on the specific needs of the application. One popular approach is to use dynamic imports, which allow components or modules to be loaded on demand, rather than all at once.

This can be achieved using tools like Webpack or Rollup, which support code splitting by generating multiple output files, each containing a subset of the application code.

React code splitting is well documented by react official

You can visit and use react code splitting, before implementing redux & redux saga code splitting,
Visit the following link : https://reactjs.org/docs/code-splitting.html


Redux code splitting

Install dependencies

we are using here all the latest packages, so we need the following dependencies for setting up the redux and redux Saga.

1. React redux

React Redux is the official react UI bindings layer for redux. It lets your React components read data from a redux store, and dispatch actions to the store to update state.

npm install react-redux

2. Redux toolkit

The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address two common concerns about Redux:

  • Configuring a Redux store is too complicated
  • Redux requires too much boilerplate code
npm install @reduxjs/toolkit

3. Redux Saga

Redux-Saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.

npm install redux-saga

Setup redux store

The redux store is a key concept in the redux library, which is a state management library for JavaScript applications.

The store is a centralized object that holds the entire state of the application, and serves as a single source of truth for all the data in the application.

create the file store.js and write following code.

import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import createSagaMiddleware from "redux-saga";
import {createSagaInjector, rootSaga} from "./sagas";
import createReducer from "./reducers";

    const sagaMiddleware = createSagaMiddleware();
    const store = configureStore({
        reducer: createReducer(),
        middleware: (gdm) => [...gdm({ serializableCheck: false }).concat([sagaMiddleware])]
    });

    store.injectSaga = createSagaInjector(sagaMiddleware.run, rootSaga);
    store.asyncReducers = {};
    store.injectReducer = (key, asyncReducer) => {
        store.asyncReducers[key] = asyncReducer;
        store.replaceReducer(createReducer(store.asyncReducers));
    };
setupListeners(store.dispatch);
export default store;

Create reducers

Here we used redux combineReducers function, for combining static reducers and asynchronous reducers. we created a function createReducer and we used this in to our store.js file.

create a reducers directory and index.js file then write the following code.

import { combineReducers } from 'redux';

import headerReducer from "./header";

const staticReducers = {
        theme: headerReducer,
};
const createReducer =  (asyncReducers) => {
    return combineReducers({
    ...staticReducers,
    ...asyncReducers,
    });
}

export default createReducer;

Create header reducer using Redux toolkit –

import { createSlice } from "@reduxjs/toolkit";


const headerSlice = createSlice({
    name: "theme",
    initialState: {
            mode: 'light',
            loading: false,
            error:'',
        },
    reducers: {
        switchThemeMode: (state) => {
            state.loading = true;
        },
        setThemeMode: (state, action) => {
            state.loading = false;
            state.mode =  action.payload;
        },
        setFailure: (state, action) => {
            state.loading = false;
            state.error =  action.payload;
        }
    }
});

export const { setThemeMode, switchThemeMode, setFailure } = headerSlice.actions;
export default headerSlice.reducer;

Redux Toolkit

Redux Toolkit is a library that simplifies the process of creating Redux stores and reduces the amount of boilerplate code needed.

It provides a set of utilities, including a createSlice() function, so that helps to streamline the process of creating Redux reducers.

The createSlice() function generates a slice of Redux state that includes a reducer function, action creators, and action types, all based on a set of input parameters.


Create Sagas

let’s create a saga createSagaInjector function for injecting sagas, and rootSaga function as this is needed in our saga injector, and we have used both of these functions in our store.js file.

create a sagas directory and index.js file then write the following code.

import { all } from 'redux-saga/effects';

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

export const createSagaInjector = (runSaga, rootSaga) => {
    const injectedSagas = new Map();
    const isInjected = key => injectedSagas.has(key);
    const injectSaga = (key, saga) => {
        if (isInjected(key)) return;
        const task = runSaga(saga);
        injectedSagas.set(key, task);
    };
    injectSaga('root', rootSaga);
    return injectSaga;
}

Create header saga using Redux Saga –

import { put, takeEvery } from 'redux-saga/effects';
import { setThemeMode, setFailure } from "./reducers/header";

function* setThemeSaga({ payload }) {
    let mode = payload;
    try {
        const isOsDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        const osMode = (isOsDark) ? 'dark' : 'light';
        const isModeExist = ('theme' in localStorage);

        mode = (isModeExist) ? ((!!mode) ? mode : localStorage.theme) : ((!!mode) ? mode : osMode);

        if (mode === 'light') {
            document.documentElement.classList.remove('dark');
            document.documentElement.classList.add('light');
            localStorage.setItem('theme', 'light');
        } else {
            document.documentElement.classList.remove('light');
            document.documentElement.classList.add('dark');
            localStorage.setItem('theme', 'dark');
        }

        yield put(setThemeMode(mode));
        } catch (error) {
            yield put(setFailure(error));

        }
    }

export function* switchThemeSaga() {
    yield takeEvery('theme/switchThemeMode', setThemeSaga);
}

Redux Saga

Redux Saga is a middleware library for Redux that helps manage asynchronous side effects in an application, such as API requests or other I/O operations. It provides a way to handle side effects in a declarative and testable way, by using generator functions.

With Redux Saga, side effects are represented as generator functions called “sagas”. A saga is a function that can listen for specific actions dispatched to the Redux store, and then perform some asynchronous operation or other side effect.


Create Header component

Create a header component for call the theme switch functionality –

import { Fragment } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import store from './store';
import { switchThemeSaga } from "./sagas/header";
import { switchThemeMode } from './reducers/header';

store.injectSaga('switchThemeSaga', switchThemeSaga);

const Header = () => {
    const dispatch = useDispatch();
    const { mode } = useSelector(state => state.theme);

    const handleThemeMode = () => {
        dispatch(switchThemeMode((mode === 'dark') ? 'light' : 'dark'));
    }

    return (
        <Fragment>
          <button type="button" onClick={handleThemeMode}>Switch Theme Mode</button>
        </Fragment>
    )

};

export default Header;
/* You can inject other reducers by the following code */

/* import accountReducer from './reducers/account'; */

/* store.injectReducer('account', accountReducer); */

Congrats! now redux and sagas are splatted, create your reducers and sagas and inject them before creating any component, so the code of the redux and sagas will split in to the components chunks as the react split the components in chunks.

This will make heavy main build file, which helps to load the app faster even if there 100s of reducers an sagas are created.

here is the example of injecting reducers and sagas –

import store from './redux/store';
import cartReducer, { getCart } from "./redux/reducers/cart";
import orderReducer, { createOrder } from "./redux/reducers/orders";
import paymentGatewaysReducer, { getPaymentGateways } from "./redux/reducers/payments";
import { getCartSaga, emptyCartSaga } from "./redux/sagas/cart";
import { getPaymentGatewaysSaga } from "./redux/sagas/payments";
import { createOrderSaga } from "./redux/sagas/orders";


store.injectReducer('cart', cartReducer);
store.injectReducer('payments', paymentGatewaysReducer);
store.injectReducer('orders', orderReducer);
store.injectSaga('getCartSaga', getCartSaga);
store.injectSaga('getPaymentGatewaysSaga', getPaymentGatewaysSaga);
store.injectSaga('createOrderSaga', createOrderSaga);
store.injectSaga('emptyCartSaga', emptyCartSaga);

Thanks for reading the article.


Source link