Skip to content

Commit 8eed0c2

Browse files
authored
Merge pull request #716 from rishabhsharma1997/redux/middleware
Redux/middleware
2 parents 52bfc6f + e60f0a4 commit 8eed0c2

File tree

8 files changed

+1507
-573
lines changed

8 files changed

+1507
-573
lines changed

package-lock.json

Lines changed: 1355 additions & 570 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@types/jest": "^29.5.11",
4949
"@types/react": "^18.2.45",
5050
"@types/react-dom": "^18.2.18",
51+
"@types/react-redux": "^7.1.33",
5152
"@typescript-eslint/eslint-plugin": "^6.12.0",
5253
"@typescript-eslint/parser": "^6.12.0",
5354
"commitizen": "^4.3.0",
@@ -67,10 +68,13 @@
6768
"prettier-plugin-organize-imports": "^3.2.3",
6869
"react-error-boundary": "^4.0.12",
6970
"react-markdown": "^8.0.7",
71+
"react-redux": "^8.1.1",
72+
"redux": "^5.0.1",
7073
"rehype-raw": "^6.1.1",
7174
"remark-gfm": "^3.0.1",
7275
"ts-jest": "^29.1.1",
73-
"tsup": "^8.0.1",
76+
"@types/lodash": "^4.17.7",
77+
"tsup": "^8.2.4",
7478
"typescript": "^5.3.3"
7579
},
7680
"peerDependencies": {
@@ -79,12 +83,16 @@
7983
"@mui/material": "^5.15.11",
8084
"@types/mui-datatables": "*",
8185
"@xstate/react": "^4.1.1",
86+
"lodash": "^4.17.21",
8287
"mui-datatables": "*",
8388
"react": ">=17",
8489
"react-dom": ">=17",
8590
"xstate": "^5.13.0"
8691
},
8792
"peerDependenciesMeta": {
93+
"lodash": {
94+
"optional": true
95+
},
8896
"@emotion/react": {
8997
"optional": true
9098
},

src/custom/Stepper/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface CustomizedStepperPropsI {
2424
stepLabels: string[];
2525
children: React.ReactNode;
2626
icons: React.ComponentType<IconProps>[];
27-
ContentWrapper?: React.ComponentType;
27+
ContentWrapper?: React.ComponentType<{ children: React.ReactNode }>;
2828
}
2929

3030
interface UseStepperI {

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ export * from './base';
33
export * from './colors';
44
export * from './custom';
55
export * from './icons';
6+
export * from './redux-persist';
67
export * from './schemas';
78
export * from './theme';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { ThunkDispatch } from '@reduxjs/toolkit';
3+
import { FC, ReactNode, useEffect, useState } from 'react';
4+
import { useDispatch } from 'react-redux';
5+
import { Dispatch } from 'redux';
6+
import { RehydrateStateAction } from './initReduxPersist';
7+
8+
interface PersistedStateProviderProps {
9+
children: ReactNode;
10+
loadPersistedState: () => (dispatch: Dispatch<RehydrateStateAction>) => void;
11+
}
12+
13+
export const PersistedStateProvider: FC<PersistedStateProviderProps> = ({
14+
children,
15+
loadPersistedState
16+
}) => {
17+
const [loading, setLoading] = useState(true);
18+
const [error, setError] = useState<Error | null>(null);
19+
20+
const dispatch = useDispatch<ThunkDispatch<any, unknown, RehydrateStateAction>>();
21+
22+
useEffect(() => {
23+
if (!loading) {
24+
return;
25+
}
26+
try {
27+
dispatch(loadPersistedState());
28+
} catch (e) {
29+
setError(e as Error);
30+
}
31+
setLoading(false);
32+
}, [loading, dispatch, loadPersistedState]);
33+
34+
if (error) {
35+
console.error('Error Loading Persisted State', error);
36+
}
37+
38+
if (loading) {
39+
return null;
40+
}
41+
42+
return <>{children}</>;
43+
};

src/redux-persist/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './PersistedStateProvider';
2+
export * from './initReduxPersist';

src/redux-persist/initReduxPersist.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import _ from 'lodash';
2+
import { Dispatch, Store } from 'redux';
3+
4+
const INVALID_REDUCER_PATH = Symbol('INVALID_REDUCER_PATH');
5+
const REHYDRATE_STATE_ACTION = 'REHYDRATE_STATE_ACTION';
6+
7+
export interface RehydrateStateAction {
8+
type: typeof REHYDRATE_STATE_ACTION;
9+
payload: {
10+
reducerPath: string;
11+
inflatedState: unknown;
12+
};
13+
}
14+
15+
type Action = RehydrateStateAction | { type: string; [key: string]: unknown };
16+
17+
interface ActionsToPersist {
18+
[actionType: string]: string[];
19+
}
20+
21+
const rehydrateState = (reducerPath: string, inflatedState: unknown): RehydrateStateAction => ({
22+
type: REHYDRATE_STATE_ACTION,
23+
payload: {
24+
reducerPath,
25+
inflatedState
26+
}
27+
});
28+
29+
const rehydrateStateReducer = (state: unknown, action: Action): unknown => {
30+
if (action.type === REHYDRATE_STATE_ACTION) {
31+
const appState = _.cloneDeep(state);
32+
_.set(
33+
appState as object,
34+
(action.payload as RehydrateStateAction['payload']).reducerPath.split('/'),
35+
(action.payload as RehydrateStateAction['payload']).inflatedState
36+
);
37+
return appState;
38+
}
39+
return state;
40+
};
41+
42+
export const initReduxPersist = (actionsToPersist: ActionsToPersist) => {
43+
const createPersistEnhancedReducer =
44+
(reducer: (state: unknown, action: Action) => unknown) =>
45+
(state: unknown, action: Action): unknown => {
46+
const newState = rehydrateStateReducer(state, action);
47+
return reducer(newState, action);
48+
};
49+
50+
const persistMiddleware =
51+
(store: Store) =>
52+
(next: (action: Action) => unknown) =>
53+
(action: Action): unknown => {
54+
const result = next(action);
55+
56+
const reducersToPersist = actionsToPersist[action.type];
57+
58+
if (reducersToPersist) {
59+
const appState = store.getState();
60+
reducersToPersist.forEach((reducerPath) => {
61+
const path = reducerPath.split('/');
62+
const stateToPersist = _.get(appState, path, INVALID_REDUCER_PATH);
63+
64+
if (stateToPersist === INVALID_REDUCER_PATH) {
65+
throw new Error(`Reducer Path to Persist Is Invalid: ${reducerPath}`);
66+
}
67+
68+
localStorage.setItem(reducerPath, JSON.stringify(stateToPersist));
69+
});
70+
}
71+
return result;
72+
};
73+
74+
const loadPersistedState = () => (dispatch: Dispatch<RehydrateStateAction>) => {
75+
Object.values(actionsToPersist).forEach((reducerPaths) => {
76+
reducerPaths.forEach((path) => {
77+
let inflatedState = localStorage.getItem(path);
78+
try {
79+
if (inflatedState) {
80+
inflatedState = JSON.parse(inflatedState);
81+
dispatch(rehydrateState(path, inflatedState));
82+
}
83+
} catch (e) {
84+
console.error(`Error rehydrating state for reducer ${path}`, inflatedState);
85+
}
86+
});
87+
});
88+
};
89+
90+
return {
91+
persistMiddleware,
92+
createPersistEnhancedReducer,
93+
loadPersistedState
94+
};
95+
};

tsup.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { defineConfig } from 'tsup';
44
const env = process.env.NODE_ENV;
55

66
export default defineConfig({
7-
outdir: 'dist',
7+
outDir: 'dist',
88
entry: ['src/index.tsx'],
99
bundle: env === 'production',
1010
clean: true,

0 commit comments

Comments
 (0)