@redux-ts-starter-kit/slice
Version:
reduce redux boilerplate
246 lines (245 loc) • 10.3 kB
TypeScript
import type { Draft } from 'immer';
import type { AnyAction, PayloadAction } from './types';
import type { MapSelectorsTo, ReMappedSelectors } from './slice-utils';
/** Type alias for case reducers when `slice` is blank or undefined */
declare type CaseReducer<S = any, P = any> = (state: Draft<S>, payload: P) => S | void | undefined;
/** Type alias for the generated reducer */
export declare type Reducer<S = any, A extends AnyAction = AnyAction> = (state: S | undefined, action: A) => S;
/**
* A map of case reducers for creating a standard reducer
* @export
* @template SS is the `SliceState`
* @template Ax is the `ActionsMap`
*/
export declare type CasesBase<SS> = {
[s: string]: CaseReducer<SS>;
};
export declare type CasesBuilder<SS, Ax extends {}> = {
[K in keyof Ax]: CaseReducer<SS, Ax[K]>;
};
/** Generic Action Map interface */
export interface ActionMap<P = any> {
[Action: string]: P;
}
export interface AnyState {
[slice: string]: any;
}
/** Type alias for generated selectors */
export declare type Selectors<SS> = {
selectSlice: (state: SS) => SS;
} & (SS extends any[] | ReadonlyArray<any> ? {} : SS extends AnyState ? {
[key in keyof SS]: (state: SS) => SS[key];
} : {});
declare type InferTypeLiteral<TyO extends string, Fallback> = TyO extends string ? TyO : Fallback extends string ? Fallback : never;
declare type InferTypeLiteralWithSliceName<SliceName extends string | '', TyO extends string, Fallback> = SliceName extends '' ? InferTypeLiteral<TyO, Fallback> : `${SliceName}/${InferTypeLiteral<TyO, Fallback>}`;
/** Type alias for generated action creators */
export declare type ActionCreatorsMap<A, TyO extends {
[K in keyof A]?: string;
} = {}, SliceName extends string = ''> = {
[key in keyof A]: ActionCreator<A[key], InferTypeLiteralWithSliceName<SliceName, NonNullable<TyO[key]>, key>> & {
type: InferTypeLiteralWithSliceName<SliceName, NonNullable<TyO[key]>, key>;
toString: () => InferTypeLiteralWithSliceName<SliceName, NonNullable<TyO[key]>, key>;
};
};
declare type ActionCreator<A, T extends string> = 0 extends A & 1 ? (payload?: any) => PayloadAction<any, T> : unknown extends A ? () => PayloadAction<undefined, T> : [undefined] extends [A] ? (payload?: A) => PayloadAction<A, T> : [null] extends [A] ? (payload: A) => PayloadAction<A, T> : keyof A extends never ? () => PayloadAction<undefined, T> : (payload: A) => PayloadAction<A, T>;
declare type PayloadTypeMap<Cases extends CasesBase<any>> = {
[K in keyof Cases]: Cases[K] extends (s: any, p: infer P) => any ? P : never;
};
export declare type ActionCreatorsMapFromCases<Cases extends CasesBase<any>, TyO = {}, SliceName extends string = ''> = ActionCreatorsMap<PayloadTypeMap<Cases>, TyO, SliceName>;
/** Map of computed selectors */
declare type ComputedMap<S> = {
[s: string]: (state: S) => any;
};
/** Hack used to make values of `typeOverrides` Object string literals */
declare type Const<TyO> = {
[K in keyof TyO]: TyO[K];
};
/**
* @interface Slice
* @description The interface of the object returned by createSlice
* @template Ax - is the [Action] creator interface
* @template SS - [SliceState]
* @template S - [State]
* @template Slc - [slice]
*/
export interface Slice<SliceName extends string, Cases extends CasesBase<SS>, SS, SelectorMap extends {
[s: string]: (state: SS) => any;
}, Computed extends {
[s: string]: (state: SS) => any;
}, TyO extends {
[K in keyof Cases]?: string;
}> {
/**
* The name of the slice
*
* @type {SliceName}
* @memberof Slice
*/
name: SliceName;
/**
* @description The generated reducer
*
* @type {Reducer<SS, AnyAction>}
* @memberof Slice
*/
reducer: Reducer<SS, AnyAction>;
/**
* An object containing the automatically generated action creators
* @type { [s:string]: (payload:any)=> PayloadAction }
* @memberof Slice
*/
actions: ActionCreatorsMapFromCases<Cases, TyO, SliceName>;
/**
* Helper utility for mapping selectors to paths
*
* @example
* // if your reducer is for a todos slice i.e
*
* const todosSlice = createSlice({
* //...
* })
*
* const rootReducer = combineReducers({
* todos: todosSlice.reducer
* });
* const store = createStore(rootReducer);
*
* const todosSelector = todosSlice.mapSelectorsTo('todos');
*
* const mapStateToProps = (state: typeof store.getState) => todosSelector.selectSlice(state)
*
* @memberof Slice
*/
reMapSelectorsTo: MapSelectorsTo<SelectorMap & Computed>;
selectors: ReMappedSelectors<[SliceName], SelectorMap & Computed>;
}
/**
* Options for `createSlice` utility
*
* @interface CreateSliceOptions
* @template SS The `SliceState` type
* @template Ax The Actions map, format - `{ [caseName]: typeof payload }`
* @template Cx The computed selectors map, format - `{ [selectorName]: returnType }`
* @template TyO The type overrides map, format - `{[caseName]: <string>typeOverride}`
*/
interface CreateSliceOptions<SliceName extends string, SS, Cases extends CasesBase<SS>, Cx extends ComputedMap<SS>, TyO> {
/**
*The slice name
*
* @type {SliceName}
* @memberof CreateSliceOptions
*/
name: SliceName;
/**
* The initial State, same as standard reducer
*
* @type {SS}
* @memberof CreateSliceOptions
*/
initialState: SS;
/**
* @description - An object whose methods represent the cases the generated reducer handles,
* can be thought of as the equivalent of [switch-case] statements in a standard reducer.
*
* @type {Cases}
* @example
* interface Todo {title: string, completed: boolean};
* const todoSlice = createSlice({
* initialState: [] as Todo[],
* cases: {
* addTodo: (state, payload: string) => {
* state.push({title: payload, completed: false})
* }
* }
* })
* @memberof CreateSliceOptions
*/
cases: Cases;
/**
* @description computed selectors for this slice, will be memoized using the `memoize-state` lib
* https://github.com/theKashey/memoize-state
*
* @see note: (js-users ignore) if using `this` to access other selectors,
* ReturnType should be explicit to prevent `typescript` errors,
* see https://github.com/dudeonyx/redux-ts-starter-kit/issues
* @example
* type Todo = {title: string, completed: boolean};
* const todosSlice = createSlice({
* initialState: [] as Todo[],
* computed: {
* getCompletedTodos: state => state.filter(todo => todo.completed),
* getCompletedTodosLength(state): number {
* return this.getCompletedTodos(state).length;
* },
* }
* cases: {
* //...
* }
* })
* @type {ComputedMap<SS, Cx>}
* @memberof CreateSliceOptions
*/
computed: Cx;
/**
* @description Type overrides to override the `type` which case reducers respond to
* which by default is simply the name of the case reducer.
* i.e the `addTodo` case reducer in `cases: { addTodo: (state, payload) => {//...}, }`
* would by default respond to actions of type `addTodo` e.g. `{ type: 'addTodo', payload: 'Jog' }`.
* This can be changed thanks to the `typeOverrides` option.
* For example the changed the type to `ADD_TODO` instead see the example below.
*
* It should be noted that the action creators `createSlice` generates automatically account for typeOverrides
*
* @example
* const todoSlice = createSlice({
* cases: {
* addTodo: (state, payload: string) => {
* state.push({title: payload, completed: false})
* },
* deleteTodo: //...,
* setCompleted: //...,
* },
* typeOverrides: {
* addTodo: 'ADD_TODO'
* }
* })
*
* @type {TyO}
* @memberof CreateSliceOptions
*/
typeOverrides?: TyO;
}
/**
* @description Generates a redux state tree slice, complete with a `reducer`,
* `action creators`, `mapSelectorsTo` and `createNameSpace`
*
* @export
* @template Actions - A map of action creator names and payloads, in the form `{[caseName]: typeof payload}`,
* a payload type of `never` | `undefined` | `void` can be used to indicate that no payload is expected
* @template SliceState - The interface of the initial state,
* @template Computed
* @template TypeOverrides
* @param {CreateSliceOptions<SliceState, Actions, Computed, TypeOverrides>} options
* @returns {(Slice<
* SliceName,
* Cases,
* SliceState,
* Selectors<SliceState>,
* AreStrictlyEqual<
* Computed,
* { [s: string]: (state: SliceState) => any } | {},
* {},
* Computed
* >,
* TyO
* >)}
* @type RemoveIndex<Computed> is needed to prevent `Computed` infering as { [s: string]: (state: SliceState) => any } when computed
* is undefined in the argument to `createSlice`. This would otherwise break type safety.
*/
export declare function createSlice<SliceName extends string, Cases extends CasesBase<SliceState>, SliceState, Computed extends ComputedMap<SliceState>, TyO extends {
[K in keyof Cases]?: string;
} = {}>({ cases, name, initialState, computed, typeOverrides, }: CreateSliceOptions<SliceName, SliceState, Cases, Computed, Const<TyO>>): Slice<SliceName, Cases, SliceState, Selectors<SliceState>, Computed, TyO>;
export declare function createSlice<SliceName extends string, Cases extends CasesBase<SliceState>, SliceState, Computed extends Record<string, never> = Record<string, never>, TyO extends {
[K in keyof Cases]?: string;
} = {}>({ cases, name, initialState, typeOverrides, }: Omit<CreateSliceOptions<SliceName, SliceState, Cases, {}, Const<TyO>>, 'computed'>): Slice<SliceName, Cases, SliceState, Selectors<SliceState>, {}, TyO>;
export {};