Redux Easy
createReduxSlice.tsx
import { bindActionCreators, createSlice, Dispatch, Reducer } from '@reduxjs/toolkit';
type SettersFor<U> = {
[K in keyof U]: U[K] extends Array<infer T>
? { set: (value: T[] | ((prev: T[]) => T[])) => void } & Array<SettersFor<T>>
: U[K] extends object
? { set: (value: U[K] | ((prev: U[K]) => U[K])) => void } & SettersFor<U[K]>
: { set: (value: U[K] | ((prev: U[K]) => U[K])) => void };
};
type ReturnSlice<T> = {
setters: SettersFor<T>,
reducer: Reducer<T>
}
export const createReduxSlice = <T extends object>(
sliceName: string,
initialState: T,
dispatch: Dispatch<any>
) : ReturnSlice<T> => {
const generateSetters = (obj: T, path: string[] = [], prefix = ''): SettersFor<T> => {
const setters: any = {};
if (!obj) return setters;
const actions: any = {};
Object.keys(obj).forEach((key) => {
const value = obj[key];
const currentPath = path.concat([key]);
const actionType = `${prefix}${key}`;
if (Array.isArray(value)) {
actions[actionType] = (newValue: any) => ({
type: `${prefix}${key}`,
payload: { path: currentPath, value: newValue },
});
setters[key] = {
set: (newValue: any) => dispatch(actions[actionType](newValue)),
...generateSetters(value, currentPath, `${actionType}.`),
};
} else if (typeof value === 'object') {
actions[actionType] = (newValue: any) => ({
type: `${prefix}${key}`,
payload: { path: currentPath, value: newValue },
});
setters[key] = {
set: (newValue: any) => dispatch(actions[actionType](newValue)),
...generateSetters(value, currentPath, `${actionType}.`),
};
} else {
actions[actionType] = (newValue: any) => ({
type: `${prefix}${key}`,
payload: { path: currentPath, value: newValue },
});
setters[key] = {
set: (newValue: any) => dispatch(actions[actionType](newValue)),
};
}
});
setters.actions = bindActionCreators(actions, dispatch);
return setters as SettersFor<T>;
};
const slice = createSlice({
name: sliceName,
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addDefaultCase((state, action) => {
const type = action?.type?.split('/')?.[0];
if (type !== sliceName) return state;
const path = action?.type?.split('/')?.[1];
const value = action?.payload?.value;
const pathParts = path?.split('.');
// console.log({ state, action, path, value })
let currentLevel = state;
for (let i = 0; i < pathParts.length - 1; i++) {
currentLevel = currentLevel[pathParts[i]];
}
const lastKey = pathParts[pathParts.length - 1];
const currentValue = currentLevel[lastKey];
if (typeof value === 'function') {
currentLevel[lastKey] = value(currentValue);
} else {
currentLevel[lastKey] = value;
}
});
},
});
return {
reducer: slice.reducer,
setters: generateSetters(initialState, [], `${sliceName}/`),
} as ReturnSlice<T>;
};
store/InventorySlice.tsx
import { createReduxSlice } from '@/hooks/createReduxSlice';
const initialState = {
tempCreateFilter: {},
bulkUpdate: {
selectedRows: [],
attrsForUpdate: []
},
viewDetails: {},
filter: {
topLevelInventoryItem: 'Operating Systems',
childLevelInventoryItem: 'Child Operating Systems',
isCaseSensitive: false,
isMatchAll: true,
crieteria: {
newItem: {} as any,
added: [] as any
},
},
singleEdit:{
category: '',
open: false,
}
}
export const InventorySlice = (sliceName, dispatch) => (
createReduxSlice(sliceName, initialState, dispatch)
)
store/index.jsx
import { bindActionCreators, combineReducers, configureStore } from '@reduxjs/toolkit';
import { useSelector } from 'react-redux';
import { InventorySlice } from './slices/InventorySlice';
import { ThemeSlice } from './slices/ThemeSlice';
type RootState = ReturnType<typeof reduxStore.getState>
const BindActionCreator =(slice,dispatch)=> {
slice.setters.actions = bindActionCreators(slice.setters.actions, dispatch);
}
export const reduxStore = configureStore({
reducer: {} as typeof rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({serializableCheck: false}),
});
// 1.1) CREATE THE SLICES
const inventorySlice = InventorySlice('InventorySlice',reduxStore.dispatch)
const themeSlice = ThemeSlice('ThemeSlice',reduxStore.dispatch)
// 1.2) COMBINE THE SLICES
const rootReducer = combineReducers({
InventorySlice: inventorySlice.reducer,
ThemeSlice: themeSlice.reducer,
});
reduxStore.replaceReducer(rootReducer);
// 1.3) BIND THE ACTION CREATORS
BindActionCreator(inventorySlice,reduxStore.dispatch);
BindActionCreator(themeSlice,reduxStore.dispatch);
// 1.4) EXPORT THE STORES
export const inventoryStore = () => ({
inventory: useSelector((state: RootState) => state.InventorySlice),
inventoryActions: inventorySlice.setters
})
export const themeStore = () => ({
theme: useSelector((state: RootState) => state.ThemeSlice),
themeActions: themeSlice.setters
})
Example usage
const {inventoryState, inventoryActions} = inventoryStore()
useEffect(() => {
inventoryActions.filter.crieteria.newItem.set(state);
}, [state])
Last updated
Was this helpful?