createSlice.d.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import type { Action, UnknownAction, Reducer } from 'redux';
  2. import type { Selector } from 'reselect';
  3. import type { ActionCreatorWithoutPayload, PayloadAction, PayloadActionCreator, PrepareAction, _ActionCreatorWithPreparedPayload } from './createAction';
  4. import type { CaseReducer } from './createReducer';
  5. import type { ActionReducerMapBuilder } from './mapBuilders';
  6. import type { Id } from './tsHelpers';
  7. import type { InjectConfig } from './combineSlices';
  8. import type { AsyncThunk, AsyncThunkConfig, AsyncThunkOptions, AsyncThunkPayloadCreator, OverrideThunkApiConfigs } from './createAsyncThunk';
  9. import { createAsyncThunk as _createAsyncThunk } from './createAsyncThunk';
  10. declare const asyncThunkSymbol: unique symbol;
  11. export declare const asyncThunkCreator: {
  12. [asyncThunkSymbol]: typeof _createAsyncThunk;
  13. };
  14. interface InjectIntoConfig<NewReducerPath extends string> extends InjectConfig {
  15. reducerPath?: NewReducerPath;
  16. }
  17. /**
  18. * The return value of `createSlice`
  19. *
  20. * @public
  21. */
  22. export interface Slice<State = any, CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>, Name extends string = string, ReducerPath extends string = Name, Selectors extends SliceSelectors<State> = SliceSelectors<State>> {
  23. /**
  24. * The slice name.
  25. */
  26. name: Name;
  27. /**
  28. * The slice reducer path.
  29. */
  30. reducerPath: ReducerPath;
  31. /**
  32. * The slice's reducer.
  33. */
  34. reducer: Reducer<State>;
  35. /**
  36. * Action creators for the types of actions that are handled by the slice
  37. * reducer.
  38. */
  39. actions: CaseReducerActions<CaseReducers, Name>;
  40. /**
  41. * The individual case reducer functions that were passed in the `reducers` parameter.
  42. * This enables reuse and testing if they were defined inline when calling `createSlice`.
  43. */
  44. caseReducers: SliceDefinedCaseReducers<CaseReducers>;
  45. /**
  46. * Provides access to the initial state value given to the slice.
  47. * If a lazy state initializer was provided, it will be called and a fresh value returned.
  48. */
  49. getInitialState: () => State;
  50. /**
  51. * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter)
  52. */
  53. getSelectors(this: this): Id<SliceDefinedSelectors<State, Selectors, State>>;
  54. /**
  55. * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
  56. */
  57. getSelectors<RootState>(this: this, selectState: (this: this, rootState: RootState) => State): Id<SliceDefinedSelectors<State, Selectors, RootState>>;
  58. /**
  59. * Selectors that assume the slice's state is `rootState[slice.reducerPath]` (which is usually the case)
  60. *
  61. * Equivalent to `slice.getSelectors((state: RootState) => state[slice.reducerPath])`.
  62. */
  63. selectors: Id<SliceDefinedSelectors<State, Selectors, {
  64. [K in ReducerPath]: State;
  65. }>>;
  66. /**
  67. * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice.
  68. */
  69. injectInto<NewReducerPath extends string = ReducerPath>(this: this, injectable: {
  70. inject: (slice: {
  71. reducerPath: string;
  72. reducer: Reducer;
  73. }, config?: InjectConfig) => void;
  74. }, config?: InjectIntoConfig<NewReducerPath>): InjectedSlice<State, CaseReducers, Name, NewReducerPath, Selectors>;
  75. /**
  76. * Select the slice state, using the slice's current reducerPath.
  77. *
  78. * Will throw an error if slice is not found.
  79. */
  80. selectSlice(this: this, state: {
  81. [K in ReducerPath]: State;
  82. }): State;
  83. }
  84. /**
  85. * A slice after being called with `injectInto(reducer)`.
  86. *
  87. * Selectors can now be called with an `undefined` value, in which case they use the slice's initial state.
  88. */
  89. interface InjectedSlice<State = any, CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>, Name extends string = string, ReducerPath extends string = Name, Selectors extends SliceSelectors<State> = SliceSelectors<State>> extends Omit<Slice<State, CaseReducers, Name, ReducerPath, Selectors>, 'getSelectors' | 'selectors'> {
  90. /**
  91. * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter)
  92. */
  93. getSelectors(): Id<SliceDefinedSelectors<State, Selectors, State | undefined>>;
  94. /**
  95. * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
  96. */
  97. getSelectors<RootState>(selectState: (this: this, rootState: RootState) => State | undefined): Id<SliceDefinedSelectors<State, Selectors, RootState>>;
  98. /**
  99. * Selectors that assume the slice's state is `rootState[slice.name]` (which is usually the case)
  100. *
  101. * Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`.
  102. */
  103. selectors: Id<SliceDefinedSelectors<State, Selectors, {
  104. [K in ReducerPath]?: State | undefined;
  105. }>>;
  106. /**
  107. * Select the slice state, using the slice's current reducerPath.
  108. *
  109. * Returns initial state if slice is not found.
  110. */
  111. selectSlice(state: {
  112. [K in ReducerPath]?: State | undefined;
  113. }): State;
  114. }
  115. /**
  116. * Options for `createSlice()`.
  117. *
  118. * @public
  119. */
  120. export interface CreateSliceOptions<State = any, CR extends SliceCaseReducers<State> = SliceCaseReducers<State>, Name extends string = string, ReducerPath extends string = Name, Selectors extends SliceSelectors<State> = SliceSelectors<State>> {
  121. /**
  122. * The slice's name. Used to namespace the generated action types.
  123. */
  124. name: Name;
  125. /**
  126. * The slice's reducer path. Used when injecting into a combined slice reducer.
  127. */
  128. reducerPath?: ReducerPath;
  129. /**
  130. * The initial state that should be used when the reducer is called the first time. This may also be a "lazy initializer" function, which should return an initial state value when called. This will be used whenever the reducer is called with `undefined` as its state value, and is primarily useful for cases like reading initial state from `localStorage`.
  131. */
  132. initialState: State | (() => State);
  133. /**
  134. * A mapping from action types to action-type-specific *case reducer*
  135. * functions. For every action type, a matching action creator will be
  136. * generated using `createAction()`.
  137. */
  138. reducers: ValidateSliceCaseReducers<State, CR> | ((creators: ReducerCreators<State>) => CR);
  139. /**
  140. * A callback that receives a *builder* object to define
  141. * case reducers via calls to `builder.addCase(actionCreatorOrType, reducer)`.
  142. *
  143. *
  144. * @example
  145. ```ts
  146. import { createAction, createSlice, Action } from '@reduxjs/toolkit'
  147. const incrementBy = createAction<number>('incrementBy')
  148. const decrement = createAction('decrement')
  149. interface RejectedAction extends Action {
  150. error: Error
  151. }
  152. function isRejectedAction(action: Action): action is RejectedAction {
  153. return action.type.endsWith('rejected')
  154. }
  155. createSlice({
  156. name: 'counter',
  157. initialState: 0,
  158. reducers: {},
  159. extraReducers: builder => {
  160. builder
  161. .addCase(incrementBy, (state, action) => {
  162. // action is inferred correctly here if using TS
  163. })
  164. // You can chain calls, or have separate `builder.addCase()` lines each time
  165. .addCase(decrement, (state, action) => {})
  166. // You can match a range of action types
  167. .addMatcher(
  168. isRejectedAction,
  169. // `action` will be inferred as a RejectedAction due to isRejectedAction being defined as a type guard
  170. (state, action) => {}
  171. )
  172. // and provide a default case if no other handlers matched
  173. .addDefaultCase((state, action) => {})
  174. }
  175. })
  176. ```
  177. */
  178. extraReducers?: (builder: ActionReducerMapBuilder<State>) => void;
  179. /**
  180. * A map of selectors that receive the slice's state and any additional arguments, and return a result.
  181. */
  182. selectors?: Selectors;
  183. }
  184. export declare enum ReducerType {
  185. reducer = "reducer",
  186. reducerWithPrepare = "reducerWithPrepare",
  187. asyncThunk = "asyncThunk"
  188. }
  189. interface ReducerDefinition<T extends ReducerType = ReducerType> {
  190. _reducerDefinitionType: T;
  191. }
  192. export interface CaseReducerDefinition<S = any, A extends Action = UnknownAction> extends CaseReducer<S, A>, ReducerDefinition<ReducerType.reducer> {
  193. }
  194. /**
  195. * A CaseReducer with a `prepare` method.
  196. *
  197. * @public
  198. */
  199. export type CaseReducerWithPrepare<State, Action extends PayloadAction> = {
  200. reducer: CaseReducer<State, Action>;
  201. prepare: PrepareAction<Action['payload']>;
  202. };
  203. export interface CaseReducerWithPrepareDefinition<State, Action extends PayloadAction> extends CaseReducerWithPrepare<State, Action>, ReducerDefinition<ReducerType.reducerWithPrepare> {
  204. }
  205. export interface AsyncThunkSliceReducerConfig<State, ThunkArg extends any, Returned = unknown, ThunkApiConfig extends AsyncThunkConfig = {}> {
  206. pending?: CaseReducer<State, ReturnType<AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['pending']>>;
  207. rejected?: CaseReducer<State, ReturnType<AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['rejected']>>;
  208. fulfilled?: CaseReducer<State, ReturnType<AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['fulfilled']>>;
  209. settled?: CaseReducer<State, ReturnType<AsyncThunk<Returned, ThunkArg, ThunkApiConfig>['rejected' | 'fulfilled']>>;
  210. options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>;
  211. }
  212. export interface AsyncThunkSliceReducerDefinition<State, ThunkArg extends any, Returned = unknown, ThunkApiConfig extends AsyncThunkConfig = {}> extends AsyncThunkSliceReducerConfig<State, ThunkArg, Returned, ThunkApiConfig>, ReducerDefinition<ReducerType.asyncThunk> {
  213. payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>;
  214. }
  215. /**
  216. * Providing these as part of the config would cause circular types, so we disallow passing them
  217. */
  218. type PreventCircular<ThunkApiConfig> = {
  219. [K in keyof ThunkApiConfig]: K extends 'state' | 'dispatch' ? never : ThunkApiConfig[K];
  220. };
  221. interface AsyncThunkCreator<State, CurriedThunkApiConfig extends PreventCircular<AsyncThunkConfig> = PreventCircular<AsyncThunkConfig>> {
  222. <ThunkArg extends any, Returned = unknown>(payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, CurriedThunkApiConfig>, config?: AsyncThunkSliceReducerConfig<State, ThunkArg, Returned, CurriedThunkApiConfig>): AsyncThunkSliceReducerDefinition<State, ThunkArg, Returned, CurriedThunkApiConfig>;
  223. <ThunkArg extends any, Returned = unknown, ThunkApiConfig extends PreventCircular<AsyncThunkConfig> = {}>(payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>, config?: AsyncThunkSliceReducerConfig<State, ThunkArg, Returned, ThunkApiConfig>): AsyncThunkSliceReducerDefinition<State, ThunkArg, Returned, ThunkApiConfig>;
  224. withTypes<ThunkApiConfig extends PreventCircular<AsyncThunkConfig>>(): AsyncThunkCreator<State, OverrideThunkApiConfigs<CurriedThunkApiConfig, ThunkApiConfig>>;
  225. }
  226. export interface ReducerCreators<State> {
  227. reducer(caseReducer: CaseReducer<State, PayloadAction>): CaseReducerDefinition<State, PayloadAction>;
  228. reducer<Payload>(caseReducer: CaseReducer<State, PayloadAction<Payload>>): CaseReducerDefinition<State, PayloadAction<Payload>>;
  229. asyncThunk: AsyncThunkCreator<State>;
  230. preparedReducer<Prepare extends PrepareAction<any>>(prepare: Prepare, reducer: CaseReducer<State, ReturnType<_ActionCreatorWithPreparedPayload<Prepare>>>): {
  231. _reducerDefinitionType: ReducerType.reducerWithPrepare;
  232. prepare: Prepare;
  233. reducer: CaseReducer<State, ReturnType<_ActionCreatorWithPreparedPayload<Prepare>>>;
  234. };
  235. }
  236. /**
  237. * The type describing a slice's `reducers` option.
  238. *
  239. * @public
  240. */
  241. export type SliceCaseReducers<State> = Record<string, CaseReducerDefinition<State, PayloadAction<any>> | CaseReducerWithPrepareDefinition<State, PayloadAction<any, string, any, any>> | AsyncThunkSliceReducerDefinition<State, any, any, any>> | Record<string, CaseReducer<State, PayloadAction<any>> | CaseReducerWithPrepare<State, PayloadAction<any, string, any, any>>>;
  242. /**
  243. * The type describing a slice's `selectors` option.
  244. */
  245. export type SliceSelectors<State> = {
  246. [K: string]: (sliceState: State, ...args: any[]) => any;
  247. };
  248. type SliceActionType<SliceName extends string, ActionName extends keyof any> = ActionName extends string | number ? `${SliceName}/${ActionName}` : string;
  249. /**
  250. * Derives the slice's `actions` property from the `reducers` options
  251. *
  252. * @public
  253. */
  254. export type CaseReducerActions<CaseReducers extends SliceCaseReducers<any>, SliceName extends string> = {
  255. [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition ? Definition extends {
  256. prepare: any;
  257. } ? ActionCreatorForCaseReducerWithPrepare<Definition, SliceActionType<SliceName, Type>> : Definition extends AsyncThunkSliceReducerDefinition<any, infer ThunkArg, infer Returned, infer ThunkApiConfig> ? AsyncThunk<Returned, ThunkArg, ThunkApiConfig> : Definition extends {
  258. reducer: any;
  259. } ? ActionCreatorForCaseReducer<Definition['reducer'], SliceActionType<SliceName, Type>> : ActionCreatorForCaseReducer<Definition, SliceActionType<SliceName, Type>> : never;
  260. };
  261. /**
  262. * Get a `PayloadActionCreator` type for a passed `CaseReducerWithPrepare`
  263. *
  264. * @internal
  265. */
  266. type ActionCreatorForCaseReducerWithPrepare<CR extends {
  267. prepare: any;
  268. }, Type extends string> = _ActionCreatorWithPreparedPayload<CR['prepare'], Type>;
  269. /**
  270. * Get a `PayloadActionCreator` type for a passed `CaseReducer`
  271. *
  272. * @internal
  273. */
  274. type ActionCreatorForCaseReducer<CR, Type extends string> = CR extends (state: any, action: infer Action) => any ? Action extends {
  275. payload: infer P;
  276. } ? PayloadActionCreator<P, Type> : ActionCreatorWithoutPayload<Type> : ActionCreatorWithoutPayload<Type>;
  277. /**
  278. * Extracts the CaseReducers out of a `reducers` object, even if they are
  279. * tested into a `CaseReducerWithPrepare`.
  280. *
  281. * @internal
  282. */
  283. type SliceDefinedCaseReducers<CaseReducers extends SliceCaseReducers<any>> = {
  284. [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition ? Definition extends AsyncThunkSliceReducerDefinition<any, any, any, any> ? Id<Pick<Required<Definition>, 'fulfilled' | 'rejected' | 'pending' | 'settled'>> : Definition extends {
  285. reducer: infer Reducer;
  286. } ? Reducer : Definition : never;
  287. };
  288. type RemappedSelector<S extends Selector, NewState> = S extends Selector<any, infer R, infer P> ? Selector<NewState, R, P> & {
  289. unwrapped: S;
  290. } : never;
  291. /**
  292. * Extracts the final selector type from the `selectors` object.
  293. *
  294. * Removes the `string` index signature from the default value.
  295. */
  296. type SliceDefinedSelectors<State, Selectors extends SliceSelectors<State>, RootState> = {
  297. [K in keyof Selectors as string extends K ? never : K]: RemappedSelector<Selectors[K], RootState>;
  298. };
  299. /**
  300. * Used on a SliceCaseReducers object.
  301. * Ensures that if a CaseReducer is a `CaseReducerWithPrepare`, that
  302. * the `reducer` and the `prepare` function use the same type of `payload`.
  303. *
  304. * Might do additional such checks in the future.
  305. *
  306. * This type is only ever useful if you want to write your own wrapper around
  307. * `createSlice`. Please don't use it otherwise!
  308. *
  309. * @public
  310. */
  311. export type ValidateSliceCaseReducers<S, ACR extends SliceCaseReducers<S>> = ACR & {
  312. [T in keyof ACR]: ACR[T] extends {
  313. reducer(s: S, action?: infer A): any;
  314. } ? {
  315. prepare(...a: never[]): Omit<A, 'type'>;
  316. } : {};
  317. };
  318. interface BuildCreateSliceConfig {
  319. creators?: {
  320. asyncThunk?: typeof asyncThunkCreator;
  321. };
  322. }
  323. export declare function buildCreateSlice({ creators }?: BuildCreateSliceConfig): <State, CaseReducers extends SliceCaseReducers<State>, Name extends string, Selectors extends SliceSelectors<State>, ReducerPath extends string = Name>(options: CreateSliceOptions<State, CaseReducers, Name, ReducerPath, Selectors>) => Slice<State, CaseReducers, Name, ReducerPath, Selectors>;
  324. /**
  325. * A function that accepts an initial state, an object full of reducer
  326. * functions, and a "slice name", and automatically generates
  327. * action creators and action types that correspond to the
  328. * reducers and state.
  329. *
  330. * @public
  331. */
  332. export declare const createSlice: <State, CaseReducers extends SliceCaseReducers<State>, Name extends string, Selectors extends SliceSelectors<State>, ReducerPath extends string = Name>(options: CreateSliceOptions<State, CaseReducers, Name, ReducerPath, Selectors>) => Slice<State, CaseReducers, Name, ReducerPath, Selectors>;
  333. export {};