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
Comments