Skip to content

Commit

Permalink
add async reducers
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMelior committed Apr 2, 2024
1 parent 007883b commit 48cb614
Show file tree
Hide file tree
Showing 28 changed files with 564 additions and 331 deletions.
7 changes: 3 additions & 4 deletions src/app/providers/ClientProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import { Theme } from '@/shared/types/theme';
import { NextUIProvider } from '@nextui-org/react';
import { ThemeProvider } from 'next-themes';
import { ReactNode } from 'react';
import { Provider } from 'react-redux';
import { store } from '../store/store';
import { StoreProvider } from './StoreProvider';

export function ClientProviders({ children }: { children: ReactNode }) {
return (
<Provider store={store}>
<StoreProvider>
<ThemeProvider attribute='class' defaultTheme={Theme.DARK}>
<NextUIProvider>{children}</NextUIProvider>
</ThemeProvider>
</Provider>
</StoreProvider>
);
}
34 changes: 34 additions & 0 deletions src/app/providers/StoreProvider/config/RootState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UserState } from '@/entities/User';
import { LoginState } from '@/features/Auth';
import { SearchState } from '@/features/Search';
import { SettingsState } from '@/features/Settings';
import { SortState } from '@/features/Sorts';
import type {
EnhancedStore,
Reducer,
ReducersMapObject,
UnknownAction,
} from '@reduxjs/toolkit';

export interface RootState {
settings: SettingsState;
user: UserState;

// Async reducers
sort?: SortState;
loginForm?: LoginState;
search?: SearchState;
}

export type RootStateKey = keyof RootState;

export interface ReducerManager {
getReducerMap: () => ReducersMapObject<RootState>;
reduce: (state: RootState, action: UnknownAction) => Partial<RootState>;
add: (key: RootStateKey, reducer: Reducer) => void;
remove: (key: RootStateKey) => void;
}

export interface ReduxStoreWithManager extends EnhancedStore<RootState> {
reducerManager: ReducerManager;
}
47 changes: 47 additions & 0 deletions src/app/providers/StoreProvider/config/reducerManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type {
Reducer,
ReducersMapObject,
UnknownAction,
} from '@reduxjs/toolkit';
import { combineReducers } from '@reduxjs/toolkit';
import { ReducerManager, RootState, RootStateKey } from './RootState';

export function createReducerManager(
initialReducers: ReducersMapObject<RootState>,
): ReducerManager {
const reducers = { ...initialReducers };
let combinedReducer = combineReducers(reducers);
let keysToRemove: RootStateKey[] = [];

return {
getReducerMap: () => reducers,
reduce: (state: RootState, action: UnknownAction) => {
if (keysToRemove.length > 0) {
state = { ...state };
for (let key of keysToRemove) {
delete state[key];
}
keysToRemove = [];
}

// @ts-ignore fix
return combinedReducer(state, action);
},
add: (key: RootStateKey, reducer: Reducer) => {
if (!key || reducers[key]) {
return;
}
reducers[key] = reducer;
combinedReducer = combineReducers(reducers);
},
remove: (key: RootStateKey) => {
if (!key || !reducers[key]) {
return;
}

delete reducers[key];
keysToRemove.push(key);
combinedReducer = combineReducers(reducers);
},
};
}
35 changes: 35 additions & 0 deletions src/app/providers/StoreProvider/config/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { userReducer } from '@/entities/User';
import { settingsReducer } from '@/features/Settings';
import type {
ReducerFromReducersMapObject,
ReducersMapObject,
} from '@reduxjs/toolkit';
import { configureStore } from '@reduxjs/toolkit';
import { RootState } from './RootState';
import { createReducerManager } from './reducerManager';

export function createReduxStore(
initialState?: RootState,
asyncReducers?: ReducersMapObject<RootState>,
) {
const rootReducers: ReducersMapObject<RootState> = {
...asyncReducers,
settings: settingsReducer,
user: userReducer,
};

const reducerManager = createReducerManager(rootReducers);

const store = configureStore<RootState>({
reducer: reducerManager.reduce as ReducerFromReducersMapObject<RootState>,
devTools: process.env.NODE_ENV === 'development', // FIX: ReferenceError: IS_DEV is not defined
preloadedState: initialState,
});

// @ts-ignore
store.reducerManager = reducerManager;

return store;
}

export type AppDispatch = ReturnType<typeof createReduxStore>['dispatch'];
11 changes: 11 additions & 0 deletions src/app/providers/StoreProvider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {
ReduxStoreWithManager,
RootState,
RootStateKey,
} from './config/RootState';
import { AppDispatch, createReduxStore } from './config/store';
import { StoreProvider } from './ui/StoreProvider';

export { StoreProvider, createReduxStore };

export type { AppDispatch, ReduxStoreWithManager, RootState, RootStateKey };
36 changes: 36 additions & 0 deletions src/app/providers/StoreProvider/ui/StoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { RootState } from '@/app/providers/StoreProvider/config/RootState';
import { createReduxStore } from '@/app/providers/StoreProvider/config/store';
import { setLocalstorage } from '@/shared/lib/features';
import { LocalstorageKeys } from '@/shared/types/localstorage';
import type { ReducersMapObject } from '@reduxjs/toolkit';
import { FC, ReactNode, useEffect } from 'react';
import { Provider } from 'react-redux';

interface StoreProviderProps {
children?: ReactNode;
initialState?: Partial<RootState>;
asyncReducers?: Partial<ReducersMapObject<RootState>>;
}

export const StoreProvider: FC<StoreProviderProps> = ({
children,
initialState,
asyncReducers,
}) => {
const store = createReduxStore(
initialState as RootState,
asyncReducers as ReducersMapObject<RootState>,
);

useEffect(() => {
const unsubscribe = store.subscribe(() => {
setLocalstorage(LocalstorageKeys.SETTINGS, store.getState().settings);
});

return () => {
unsubscribe();
};
}, [store]);

return <Provider store={store}>{children}</Provider>;
};
26 changes: 0 additions & 26 deletions src/app/store/store.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { RootState } from '@/app/store/store';
import { RootState } from '@/app/providers/StoreProvider';

export const getUserAuthData = (state: RootState) => state.user.authData;
4 changes: 2 additions & 2 deletions src/entities/User/model/slice/userSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { User, UserState } from '../types/user';

const authData = getLocalstorage<User>(LocalstorageKeys.USER);

const initialState: UserState = { authData };
export const userInitialState: UserState = { authData };

export const userSlice = createSlice({
name: 'user',
initialState,
initialState: userInitialState,
reducers: {
setAuthData: (state, action: PayloadAction<User>) => {
state.authData = action.payload;
Expand Down
2 changes: 1 addition & 1 deletion src/features/Auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { loginByUsername } from './model/service/loginByUsername';
import { loginActions, loginReducer } from './model/slice/loginSlice';
import { LoginState } from './model/types/loginState';
import { ModalLogin } from './ui/ModalLogin/ModalLogin';
import ModalLogin from './ui/ModalLogin/ModalLogin';

export { ModalLogin, loginActions, loginByUsername, loginReducer };
export type { LoginState };
6 changes: 4 additions & 2 deletions src/features/Auth/model/selector/getLoginState.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { RootState } from '@/app/store/store';
import { RootState } from '@/app/providers/StoreProvider';
import { loginInitialState } from '../slice/loginSlice';

export const getLoginState = (state: RootState) => state?.loginForm;
export const getLoginState = (state: RootState) =>
state?.loginForm ?? loginInitialState;
4 changes: 2 additions & 2 deletions src/features/Auth/model/slice/loginSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { createSlice } from '@reduxjs/toolkit';
import { loginByUsername } from '../..';
import { LoginState } from '../types/loginState';

const initialState: LoginState = {
export const loginInitialState: LoginState = {
username: '',
password: '',
isLoading: false,
};

export const loginSlice = createSlice({
name: 'login',
initialState,
initialState: loginInitialState,
reducers: {
setUsername: (state, action: PayloadAction<string>) => {
state.username = action.payload;
Expand Down
Loading

0 comments on commit 48cb614

Please sign in to comment.