Reputation: 400
I am using @reduxjs/toolkit
and want to create an easily extendible function that creates a slice with default reducers. The implementation that I have now works but is not strongly typed. How can I create a function so that the slice's actions type contains not only the default reducers but also the ones that are passed in? I have tried using inference types but could not get it to work.
Any guidance would be appreciated. Thanks.
Minimum example:
in common.ts
file (where logic can be shared between slices)
export interface StoreState<T> {
data: T
status: 'succeeded' | 'failed' | 'idle'
error: string | null
}
// create a slice given a name and make it possible to extend reducers so they include more than just reset and updateStatus
export const createStoreSlice = <T>(props: {
sliceName: string
defaultState: T
reducers?: SliceCaseReducers<StoreState<T>> // <-- want to infer this in slices/<sliceName>.ts
}) => {
const { sliceName, reducers, defaultState } = props
const initialState: StoreState<T> = {
data: defaultState,
status: 'idle',
error: null,
}
return createSlice({
name: sliceName,
initialState,
reducers: {
...reducers, // <--- want to somehow infer the type of this when exporting slice actions
reset: (state) => {
Object.assign(state, initialState)
},
updateStatus: (state, action) => {
state.status = action.payload
},
},
})
}
in slices/<sliceName>.ts
(specific slices with extra logic)
export const genericSlice = createStoreSlice({
sliceName: 'someSliceName',
defaultState: { someField: 'some value' },
reducers: {
setSomeField: (state, action) => {
const { payload } = action
state.data.someField = payload
},
},
})
// these actions should be strongly typed from the createStoreSlice function parameters and contain the default reducers (eg. reset, updateStatus) and extra ones specific to the slice (eg. setSomeField)
export const { reset, updateStatus, setSomeField } = genericSlice.actions
Upvotes: 8
Views: 1681
Reputation: 4327
You were really close but there were three parts you were missing
reducers
with a generic typereducers
type (i.e. R
) along with the provided ValidateSliceCaseReducers
type to determine the resulting type for reducers
.reducers
type required. I was only able to make it work with required reducers but there may be a way around this but unlikely as the types come from @reduxjs/toolkit
.Note: I just pulled out the
createStoreSlice
props into theStoreSliceProps
type to make it easier to read.
import { SliceCaseReducers, createSlice, ValidateSliceCaseReducers } from '@reduxjs/toolkit';
export interface StoreState<T> {
data: T;
status: 'succeeded' | 'failed' | 'idle';
error: string | null;
}
interface StoreSliceProps<T, R extends SliceCaseReducers<StoreState<T>>> {
sliceName: string;
defaultState: T;
reducers: ValidateSliceCaseReducers<StoreState<T>, R>;
}
export function createStoreSlice<T, R extends SliceCaseReducers<StoreState<T>>>(props: StoreSliceProps<T, R>) {
const { sliceName, reducers, defaultState } = props;
const initialState: StoreState<T> = {
data: defaultState,
status: 'idle',
error: null,
};
return createSlice({
name: sliceName,
initialState,
reducers: {
...reducers,
reset: (state) => {
Object.assign(state, initialState);
},
updateStatus: (state, action) => {
state.status = action.payload;
},
},
});
};
export const genericSlice = createStoreSlice({
sliceName: 'someSliceName',
defaultState: { someField: 'some value' },
reducers: {
setSomeField: (state, action) => {
const { payload } = action;
state.data.someField = payload;
},
},
});
export const { reset, updateStatus, setSomeField, fakeReducer } = genericSlice.actions; // only fakeReducer throws error as unknown as expected
Here is a working TS Playground
See this example from their docs that explains it a bit better.
Upvotes: 6