Redux Easy
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>;
};
Example usage
Last updated
Was this helpful?