Code splitting with redux!

The store is smaller better performance

The Problem

When we code react with redux, sometimes, we don't need some state but it is just global. Simply, it's always existed in our web app? How to resolve it? How to remove all unused global state?

Code splitting

Based on the redux docs:

In large web applications, it is often desirable to split up the app code into multiple JS bundles that can be loaded on-demand. This strategy, called 'code splitting', helps to increase performance of your application by reducing the size of the initial JS payload that must be fetched.

The words you need to know about the code splitting redux

  • replaceReducer: The function allow us to replace the current reducer to create new global state
  • reducer manager: The function will add/ remove the reducer by a key
  • Static reducers: This mean is all reducers always exsist in our app - like authReducer or themeReducer
  • Dynamic reducer: Reducers will be added when we need and remove when the state is unsuseful

The Idea

We will use the reducer manager to manage the reducer.

And how do we know what the reducer need or need't? We will base on the react router (or some routing what you use)

The Implementation

Static reducer

// store/staticReducer.ts

import { TObjectAction } from "../actions/types";

export const initialState = {
  msg: "State always present in your store"
};

export const staticReducer = (state = initialState, action: TObjectAction) => {
  switch (action.type) {
    case "GET":
      return state;
    default:
      return state;
  }
};

Then we write the dynamic reducer

import { User } from "../../types";
import { RandomUserAction } from "../actions/randomUser";
import { TObjectAction } from "../actions/types";

export const randomUserDataKey = "randomUserData";

export const randomUserStateKey = "randomUser";

export type TState = {
  [randomUserDataKey]: {
    result: User[];
  };
  isLoading: boolean;
};

export const initialState: TState = {
  [randomUserDataKey]: {
    result: []
  },
  isLoading: false
};

export const randomUserReducer = (
  state = initialState,
  action: TObjectAction
) => {
  switch (action.type) {
    case RandomUserAction.CLEAR_FETCH_RESULTS:
      state = initialState;
      return state;
    default:
      return state;
  }
};

randomUserStateKey used to be the key for the dynamic reducer, we add and remove it when we need or need't

follow the redux docs, we can write the reducer manager

import { combineReducers, createStore, Store } from "redux";
import {
  staticReducer,
  initialState as initialStateStatic
} from "../reducers/staticReducer";

export function createReducerManager(initialReducers: any) {
  // Create an object which maps keys to reducers
  const reducers = { ...initialReducers };

  // Create the initial combinedReducer
  let combinedReducer = combineReducers(reducers);

  // An array which is used to delete state keys when reducers are removed
  let keysToRemove: string[] = [];

  return {
    getReducerMap: () => reducers,

    // The root reducer function exposed by this object
    // This will be passed to the store
    reduce: (state: any, action: any) => {
      // If any reducers have been removed, clean up their state first
      if (keysToRemove.length > 0) {
        state = { ...state };
        for (let key of keysToRemove) {
          delete state[key];
        }
        keysToRemove = [];
      }

      // Delegate to the combined reducer
      // @ts-ignore
      return combinedReducer(state, action);
    },

    // Adds a new reducer with the specified key
    add: (key: string, reducer: any) => {
      if (!key || reducers[key]) {
        return;
      }

      // Add the reducer to the reducer mapping
      reducers[key] = reducer;

      // Generate a new combined reducer
      combinedReducer = combineReducers(reducers);
    },

    // Removes a reducer with the specified key
    remove: (key: string) => {
      if (!key || !reducers[key]) {
        return;
      }

      // Remove it from the reducer mapping
      delete reducers[key];

      // Add the key to the list of keys to clean up
      keysToRemove.push(key);

      // Generate a new combined reducer
      combinedReducer = combineReducers(reducers);
    }
  };
}

const staticReducers = { static: staticReducer };

type TStore = {
  reducerManager: ReturnType<typeof createReducerManager>;
} & Store;

const initialState = {
  static: initialStateStatic
};

export function configureStore(rest: any) {
  const reducerManager = createReducerManager(staticReducers);

  // Create a store with the root reducer function being the one exposed by the manager.
  const store = createStore(
    reducerManager.reduce,
    initialState,
    rest
  ) as TStore;

  // Optional: Put the reducer manager on the store so it is easily accessible
  store.reducerManager = reducerManager;

  return store;
}

Static reducers will alway present in your web app. We will use the function add and remove to manage all reducers inside store

Removing or adding the needed reducer

Writing a HOC wrap all the app. This HOC will check the URL and then add or remove the reducer

import React, { useMemo } from "react";
import { withRouter } from "react-router";
import { TRouteName } from "../types/route";
import store from "../store";
import {
  randomUserStateKey,
  randomUserReducer
} from "../store/reducers/randomUser";
import { fetchReducer } from "../store/reducers/fetchReducer";

export const InjectReducer = withRouter(({ history, children }) => {
  const { pathname } = history.location;

  return useMemo(() => {
    switch (pathname as TRouteName) {
      case "/random-user":
        store.reducerManager.add(
          randomUserStateKey,
          fetchReducer(randomUserReducer)
        );
        store.reducerManager.remove(someReducer);
        break;
      case "/some-route":
        store.reducerManager.add(someReducer);
        store.reducerManager.remove(randomUserStateKey);
        break;
      default:
        break;
    }

    return <>{children}</>;
  }, [pathname, children]);
});

We just need the randomUserReducer on /random-user only, so we remove all except it. And in the /some-route, we remove randomUserReducer and add someReducer

Demo

The code above is just present the ideal. If you want to understand more and see the result, you can run the playground with my codesanbox

Source

Comments

Contact for work:Skype
Code from my 💕