| 1 | import type { Action, Reducer, UnknownAction } from 'redux'
|
|---|
| 2 | import type { Selector } from 'reselect'
|
|---|
| 3 | import type { InjectConfig } from './combineSlices'
|
|---|
| 4 | import type {
|
|---|
| 5 | ActionCreatorWithoutPayload,
|
|---|
| 6 | PayloadAction,
|
|---|
| 7 | PayloadActionCreator,
|
|---|
| 8 | PrepareAction,
|
|---|
| 9 | _ActionCreatorWithPreparedPayload,
|
|---|
| 10 | } from './createAction'
|
|---|
| 11 | import { createAction } from './createAction'
|
|---|
| 12 | import type {
|
|---|
| 13 | AsyncThunk,
|
|---|
| 14 | AsyncThunkConfig,
|
|---|
| 15 | AsyncThunkOptions,
|
|---|
| 16 | AsyncThunkPayloadCreator,
|
|---|
| 17 | OverrideThunkApiConfigs,
|
|---|
| 18 | } from './createAsyncThunk'
|
|---|
| 19 | import { createAsyncThunk as _createAsyncThunk } from './createAsyncThunk'
|
|---|
| 20 | import type {
|
|---|
| 21 | ActionMatcherDescriptionCollection,
|
|---|
| 22 | CaseReducer,
|
|---|
| 23 | ReducerWithInitialState,
|
|---|
| 24 | } from './createReducer'
|
|---|
| 25 | import { createReducer } from './createReducer'
|
|---|
| 26 | import type {
|
|---|
| 27 | ActionReducerMapBuilder,
|
|---|
| 28 | AsyncThunkReducers,
|
|---|
| 29 | TypedActionCreator,
|
|---|
| 30 | } from './mapBuilders'
|
|---|
| 31 | import { executeReducerBuilderCallback } from './mapBuilders'
|
|---|
| 32 | import type { Id, TypeGuard } from './tsHelpers'
|
|---|
| 33 | import { getOrInsertComputed } from './utils'
|
|---|
| 34 |
|
|---|
| 35 | const asyncThunkSymbol = /* @__PURE__ */ Symbol.for(
|
|---|
| 36 | 'rtk-slice-createasyncthunk',
|
|---|
| 37 | )
|
|---|
| 38 | // type is annotated because it's too long to infer
|
|---|
| 39 | export const asyncThunkCreator: {
|
|---|
| 40 | [asyncThunkSymbol]: typeof _createAsyncThunk
|
|---|
| 41 | } = {
|
|---|
| 42 | [asyncThunkSymbol]: _createAsyncThunk,
|
|---|
| 43 | }
|
|---|
| 44 |
|
|---|
| 45 | type InjectIntoConfig<NewReducerPath extends string> = InjectConfig & {
|
|---|
| 46 | reducerPath?: NewReducerPath
|
|---|
| 47 | }
|
|---|
| 48 |
|
|---|
| 49 | /**
|
|---|
| 50 | * The return value of `createSlice`
|
|---|
| 51 | *
|
|---|
| 52 | * @public
|
|---|
| 53 | */
|
|---|
| 54 | export interface Slice<
|
|---|
| 55 | State = any,
|
|---|
| 56 | CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>,
|
|---|
| 57 | Name extends string = string,
|
|---|
| 58 | ReducerPath extends string = Name,
|
|---|
| 59 | Selectors extends SliceSelectors<State> = SliceSelectors<State>,
|
|---|
| 60 | > {
|
|---|
| 61 | /**
|
|---|
| 62 | * The slice name.
|
|---|
| 63 | */
|
|---|
| 64 | name: Name
|
|---|
| 65 |
|
|---|
| 66 | /**
|
|---|
| 67 | * The slice reducer path.
|
|---|
| 68 | */
|
|---|
| 69 | reducerPath: ReducerPath
|
|---|
| 70 |
|
|---|
| 71 | /**
|
|---|
| 72 | * The slice's reducer.
|
|---|
| 73 | */
|
|---|
| 74 | reducer: Reducer<State>
|
|---|
| 75 |
|
|---|
| 76 | /**
|
|---|
| 77 | * Action creators for the types of actions that are handled by the slice
|
|---|
| 78 | * reducer.
|
|---|
| 79 | */
|
|---|
| 80 | actions: CaseReducerActions<CaseReducers, Name>
|
|---|
| 81 |
|
|---|
| 82 | /**
|
|---|
| 83 | * The individual case reducer functions that were passed in the `reducers` parameter.
|
|---|
| 84 | * This enables reuse and testing if they were defined inline when calling `createSlice`.
|
|---|
| 85 | */
|
|---|
| 86 | caseReducers: SliceDefinedCaseReducers<CaseReducers>
|
|---|
| 87 |
|
|---|
| 88 | /**
|
|---|
| 89 | * Provides access to the initial state value given to the slice.
|
|---|
| 90 | * If a lazy state initializer was provided, it will be called and a fresh value returned.
|
|---|
| 91 | */
|
|---|
| 92 | getInitialState: () => State
|
|---|
| 93 |
|
|---|
| 94 | /**
|
|---|
| 95 | * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter)
|
|---|
| 96 | */
|
|---|
| 97 | getSelectors(): Id<SliceDefinedSelectors<State, Selectors, State>>
|
|---|
| 98 |
|
|---|
| 99 | /**
|
|---|
| 100 | * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
|
|---|
| 101 | */
|
|---|
| 102 | getSelectors<RootState>(
|
|---|
| 103 | selectState: (rootState: RootState) => State,
|
|---|
| 104 | ): Id<SliceDefinedSelectors<State, Selectors, RootState>>
|
|---|
| 105 |
|
|---|
| 106 | /**
|
|---|
| 107 | * Selectors that assume the slice's state is `rootState[slice.reducerPath]` (which is usually the case)
|
|---|
| 108 | *
|
|---|
| 109 | * Equivalent to `slice.getSelectors((state: RootState) => state[slice.reducerPath])`.
|
|---|
| 110 | */
|
|---|
| 111 | get selectors(): Id<
|
|---|
| 112 | SliceDefinedSelectors<State, Selectors, { [K in ReducerPath]: State }>
|
|---|
| 113 | >
|
|---|
| 114 |
|
|---|
| 115 | /**
|
|---|
| 116 | * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice.
|
|---|
| 117 | */
|
|---|
| 118 | injectInto<NewReducerPath extends string = ReducerPath>(
|
|---|
| 119 | this: this,
|
|---|
| 120 | injectable: {
|
|---|
| 121 | inject: (
|
|---|
| 122 | slice: { reducerPath: string; reducer: Reducer },
|
|---|
| 123 | config?: InjectConfig,
|
|---|
| 124 | ) => void
|
|---|
| 125 | },
|
|---|
| 126 | config?: InjectIntoConfig<NewReducerPath>,
|
|---|
| 127 | ): InjectedSlice<State, CaseReducers, Name, NewReducerPath, Selectors>
|
|---|
| 128 |
|
|---|
| 129 | /**
|
|---|
| 130 | * Select the slice state, using the slice's current reducerPath.
|
|---|
| 131 | *
|
|---|
| 132 | * Will throw an error if slice is not found.
|
|---|
| 133 | */
|
|---|
| 134 | selectSlice(state: { [K in ReducerPath]: State }): State
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | /**
|
|---|
| 138 | * A slice after being called with `injectInto(reducer)`.
|
|---|
| 139 | *
|
|---|
| 140 | * Selectors can now be called with an `undefined` value, in which case they use the slice's initial state.
|
|---|
| 141 | */
|
|---|
| 142 | type InjectedSlice<
|
|---|
| 143 | State = any,
|
|---|
| 144 | CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>,
|
|---|
| 145 | Name extends string = string,
|
|---|
| 146 | ReducerPath extends string = Name,
|
|---|
| 147 | Selectors extends SliceSelectors<State> = SliceSelectors<State>,
|
|---|
| 148 | > = Omit<
|
|---|
| 149 | Slice<State, CaseReducers, Name, ReducerPath, Selectors>,
|
|---|
| 150 | 'getSelectors' | 'selectors'
|
|---|
| 151 | > & {
|
|---|
| 152 | /**
|
|---|
| 153 | * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter)
|
|---|
| 154 | */
|
|---|
| 155 | getSelectors(): Id<SliceDefinedSelectors<State, Selectors, State | undefined>>
|
|---|
| 156 |
|
|---|
| 157 | /**
|
|---|
| 158 | * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
|
|---|
| 159 | */
|
|---|
| 160 | getSelectors<RootState>(
|
|---|
| 161 | selectState: (rootState: RootState) => State | undefined,
|
|---|
| 162 | ): Id<SliceDefinedSelectors<State, Selectors, RootState>>
|
|---|
| 163 |
|
|---|
| 164 | /**
|
|---|
| 165 | * Selectors that assume the slice's state is `rootState[slice.name]` (which is usually the case)
|
|---|
| 166 | *
|
|---|
| 167 | * Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`.
|
|---|
| 168 | */
|
|---|
| 169 | get selectors(): Id<
|
|---|
| 170 | SliceDefinedSelectors<
|
|---|
| 171 | State,
|
|---|
| 172 | Selectors,
|
|---|
| 173 | { [K in ReducerPath]?: State | undefined }
|
|---|
| 174 | >
|
|---|
| 175 | >
|
|---|
| 176 |
|
|---|
| 177 | /**
|
|---|
| 178 | * Select the slice state, using the slice's current reducerPath.
|
|---|
| 179 | *
|
|---|
| 180 | * Returns initial state if slice is not found.
|
|---|
| 181 | */
|
|---|
| 182 | selectSlice(state: { [K in ReducerPath]?: State | undefined }): State
|
|---|
| 183 | }
|
|---|
| 184 |
|
|---|
| 185 | /**
|
|---|
| 186 | * Options for `createSlice()`.
|
|---|
| 187 | *
|
|---|
| 188 | * @public
|
|---|
| 189 | */
|
|---|
| 190 | export interface CreateSliceOptions<
|
|---|
| 191 | State = any,
|
|---|
| 192 | CR extends SliceCaseReducers<State> = SliceCaseReducers<State>,
|
|---|
| 193 | Name extends string = string,
|
|---|
| 194 | ReducerPath extends string = Name,
|
|---|
| 195 | Selectors extends SliceSelectors<State> = SliceSelectors<State>,
|
|---|
| 196 | > {
|
|---|
| 197 | /**
|
|---|
| 198 | * The slice's name. Used to namespace the generated action types.
|
|---|
| 199 | */
|
|---|
| 200 | name: Name
|
|---|
| 201 |
|
|---|
| 202 | /**
|
|---|
| 203 | * The slice's reducer path. Used when injecting into a combined slice reducer.
|
|---|
| 204 | */
|
|---|
| 205 | reducerPath?: ReducerPath
|
|---|
| 206 |
|
|---|
| 207 | /**
|
|---|
| 208 | * 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`.
|
|---|
| 209 | */
|
|---|
| 210 | initialState: State | (() => State)
|
|---|
| 211 |
|
|---|
| 212 | /**
|
|---|
| 213 | * A mapping from action types to action-type-specific *case reducer*
|
|---|
| 214 | * functions. For every action type, a matching action creator will be
|
|---|
| 215 | * generated using `createAction()`.
|
|---|
| 216 | */
|
|---|
| 217 | reducers:
|
|---|
| 218 | | ValidateSliceCaseReducers<State, CR>
|
|---|
| 219 | | ((creators: ReducerCreators<State>) => CR)
|
|---|
| 220 |
|
|---|
| 221 | /**
|
|---|
| 222 | * A callback that receives a *builder* object to define
|
|---|
| 223 | * case reducers via calls to `builder.addCase(actionCreatorOrType, reducer)`.
|
|---|
| 224 | *
|
|---|
| 225 | *
|
|---|
| 226 | * @example
|
|---|
| 227 | ```ts
|
|---|
| 228 | import { createAction, createSlice, Action } from '@reduxjs/toolkit'
|
|---|
| 229 | const incrementBy = createAction<number>('incrementBy')
|
|---|
| 230 | const decrement = createAction('decrement')
|
|---|
| 231 |
|
|---|
| 232 | interface RejectedAction extends Action {
|
|---|
| 233 | error: Error
|
|---|
| 234 | }
|
|---|
| 235 |
|
|---|
| 236 | function isRejectedAction(action: Action): action is RejectedAction {
|
|---|
| 237 | return action.type.endsWith('rejected')
|
|---|
| 238 | }
|
|---|
| 239 |
|
|---|
| 240 | createSlice({
|
|---|
| 241 | name: 'counter',
|
|---|
| 242 | initialState: 0,
|
|---|
| 243 | reducers: {},
|
|---|
| 244 | extraReducers: builder => {
|
|---|
| 245 | builder
|
|---|
| 246 | .addCase(incrementBy, (state, action) => {
|
|---|
| 247 | // action is inferred correctly here if using TS
|
|---|
| 248 | })
|
|---|
| 249 | // You can chain calls, or have separate `builder.addCase()` lines each time
|
|---|
| 250 | .addCase(decrement, (state, action) => {})
|
|---|
| 251 | // You can match a range of action types
|
|---|
| 252 | .addMatcher(
|
|---|
| 253 | isRejectedAction,
|
|---|
| 254 | // `action` will be inferred as a RejectedAction due to isRejectedAction being defined as a type guard
|
|---|
| 255 | (state, action) => {}
|
|---|
| 256 | )
|
|---|
| 257 | // and provide a default case if no other handlers matched
|
|---|
| 258 | .addDefaultCase((state, action) => {})
|
|---|
| 259 | }
|
|---|
| 260 | })
|
|---|
| 261 | ```
|
|---|
| 262 | */
|
|---|
| 263 | extraReducers?: (builder: ActionReducerMapBuilder<State>) => void
|
|---|
| 264 |
|
|---|
| 265 | /**
|
|---|
| 266 | * A map of selectors that receive the slice's state and any additional arguments, and return a result.
|
|---|
| 267 | */
|
|---|
| 268 | selectors?: Selectors
|
|---|
| 269 | }
|
|---|
| 270 |
|
|---|
| 271 | export enum ReducerType {
|
|---|
| 272 | reducer = 'reducer',
|
|---|
| 273 | reducerWithPrepare = 'reducerWithPrepare',
|
|---|
| 274 | asyncThunk = 'asyncThunk',
|
|---|
| 275 | }
|
|---|
| 276 |
|
|---|
| 277 | type ReducerDefinition<T extends ReducerType = ReducerType> = {
|
|---|
| 278 | _reducerDefinitionType: T
|
|---|
| 279 | }
|
|---|
| 280 |
|
|---|
| 281 | export type CaseReducerDefinition<
|
|---|
| 282 | S = any,
|
|---|
| 283 | A extends Action = UnknownAction,
|
|---|
| 284 | > = CaseReducer<S, A> & ReducerDefinition<ReducerType.reducer>
|
|---|
| 285 |
|
|---|
| 286 | /**
|
|---|
| 287 | * A CaseReducer with a `prepare` method.
|
|---|
| 288 | *
|
|---|
| 289 | * @public
|
|---|
| 290 | */
|
|---|
| 291 | export type CaseReducerWithPrepare<State, Action extends PayloadAction> = {
|
|---|
| 292 | reducer: CaseReducer<State, Action>
|
|---|
| 293 | prepare: PrepareAction<Action['payload']>
|
|---|
| 294 | }
|
|---|
| 295 |
|
|---|
| 296 | export interface CaseReducerWithPrepareDefinition<
|
|---|
| 297 | State,
|
|---|
| 298 | Action extends PayloadAction,
|
|---|
| 299 | > extends CaseReducerWithPrepare<State, Action>,
|
|---|
| 300 | ReducerDefinition<ReducerType.reducerWithPrepare> {}
|
|---|
| 301 |
|
|---|
| 302 | type AsyncThunkSliceReducerConfig<
|
|---|
| 303 | State,
|
|---|
| 304 | ThunkArg extends any,
|
|---|
| 305 | Returned = unknown,
|
|---|
| 306 | ThunkApiConfig extends AsyncThunkConfig = {},
|
|---|
| 307 | > = AsyncThunkReducers<State, ThunkArg, Returned, ThunkApiConfig> & {
|
|---|
| 308 | options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>
|
|---|
| 309 | }
|
|---|
| 310 |
|
|---|
| 311 | type AsyncThunkSliceReducerDefinition<
|
|---|
| 312 | State,
|
|---|
| 313 | ThunkArg extends any,
|
|---|
| 314 | Returned = unknown,
|
|---|
| 315 | ThunkApiConfig extends AsyncThunkConfig = {},
|
|---|
| 316 | > = AsyncThunkSliceReducerConfig<State, ThunkArg, Returned, ThunkApiConfig> &
|
|---|
| 317 | ReducerDefinition<ReducerType.asyncThunk> & {
|
|---|
| 318 | payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>
|
|---|
| 319 | }
|
|---|
| 320 |
|
|---|
| 321 | /**
|
|---|
| 322 | * Providing these as part of the config would cause circular types, so we disallow passing them
|
|---|
| 323 | */
|
|---|
| 324 | type PreventCircular<ThunkApiConfig> = {
|
|---|
| 325 | [K in keyof ThunkApiConfig]: K extends 'state' | 'dispatch'
|
|---|
| 326 | ? never
|
|---|
| 327 | : ThunkApiConfig[K]
|
|---|
| 328 | }
|
|---|
| 329 |
|
|---|
| 330 | interface AsyncThunkCreator<
|
|---|
| 331 | State,
|
|---|
| 332 | CurriedThunkApiConfig extends
|
|---|
| 333 | PreventCircular<AsyncThunkConfig> = PreventCircular<AsyncThunkConfig>,
|
|---|
| 334 | > {
|
|---|
| 335 | <Returned, ThunkArg = void>(
|
|---|
| 336 | payloadCreator: AsyncThunkPayloadCreator<
|
|---|
| 337 | Returned,
|
|---|
| 338 | ThunkArg,
|
|---|
| 339 | CurriedThunkApiConfig
|
|---|
| 340 | >,
|
|---|
| 341 | config?: AsyncThunkSliceReducerConfig<
|
|---|
| 342 | State,
|
|---|
| 343 | ThunkArg,
|
|---|
| 344 | Returned,
|
|---|
| 345 | CurriedThunkApiConfig
|
|---|
| 346 | >,
|
|---|
| 347 | ): AsyncThunkSliceReducerDefinition<
|
|---|
| 348 | State,
|
|---|
| 349 | ThunkArg,
|
|---|
| 350 | Returned,
|
|---|
| 351 | CurriedThunkApiConfig
|
|---|
| 352 | >
|
|---|
| 353 | <
|
|---|
| 354 | Returned,
|
|---|
| 355 | ThunkArg,
|
|---|
| 356 | ThunkApiConfig extends PreventCircular<AsyncThunkConfig> = {},
|
|---|
| 357 | >(
|
|---|
| 358 | payloadCreator: AsyncThunkPayloadCreator<
|
|---|
| 359 | Returned,
|
|---|
| 360 | ThunkArg,
|
|---|
| 361 | ThunkApiConfig
|
|---|
| 362 | >,
|
|---|
| 363 | config?: AsyncThunkSliceReducerConfig<
|
|---|
| 364 | State,
|
|---|
| 365 | ThunkArg,
|
|---|
| 366 | Returned,
|
|---|
| 367 | ThunkApiConfig
|
|---|
| 368 | >,
|
|---|
| 369 | ): AsyncThunkSliceReducerDefinition<State, ThunkArg, Returned, ThunkApiConfig>
|
|---|
| 370 | withTypes<
|
|---|
| 371 | ThunkApiConfig extends PreventCircular<AsyncThunkConfig>,
|
|---|
| 372 | >(): AsyncThunkCreator<
|
|---|
| 373 | State,
|
|---|
| 374 | OverrideThunkApiConfigs<CurriedThunkApiConfig, ThunkApiConfig>
|
|---|
| 375 | >
|
|---|
| 376 | }
|
|---|
| 377 |
|
|---|
| 378 | export interface ReducerCreators<State> {
|
|---|
| 379 | reducer(
|
|---|
| 380 | caseReducer: CaseReducer<State, PayloadAction>,
|
|---|
| 381 | ): CaseReducerDefinition<State, PayloadAction>
|
|---|
| 382 | reducer<Payload>(
|
|---|
| 383 | caseReducer: CaseReducer<State, PayloadAction<Payload>>,
|
|---|
| 384 | ): CaseReducerDefinition<State, PayloadAction<Payload>>
|
|---|
| 385 |
|
|---|
| 386 | asyncThunk: AsyncThunkCreator<State>
|
|---|
| 387 |
|
|---|
| 388 | preparedReducer<Prepare extends PrepareAction<any>>(
|
|---|
| 389 | prepare: Prepare,
|
|---|
| 390 | reducer: CaseReducer<
|
|---|
| 391 | State,
|
|---|
| 392 | ReturnType<_ActionCreatorWithPreparedPayload<Prepare>>
|
|---|
| 393 | >,
|
|---|
| 394 | ): {
|
|---|
| 395 | _reducerDefinitionType: ReducerType.reducerWithPrepare
|
|---|
| 396 | prepare: Prepare
|
|---|
| 397 | reducer: CaseReducer<
|
|---|
| 398 | State,
|
|---|
| 399 | ReturnType<_ActionCreatorWithPreparedPayload<Prepare>>
|
|---|
| 400 | >
|
|---|
| 401 | }
|
|---|
| 402 | }
|
|---|
| 403 |
|
|---|
| 404 | /**
|
|---|
| 405 | * The type describing a slice's `reducers` option.
|
|---|
| 406 | *
|
|---|
| 407 | * @public
|
|---|
| 408 | */
|
|---|
| 409 | export type SliceCaseReducers<State> =
|
|---|
| 410 | | Record<string, ReducerDefinition>
|
|---|
| 411 | | Record<
|
|---|
| 412 | string,
|
|---|
| 413 | | CaseReducer<State, PayloadAction<any>>
|
|---|
| 414 | | CaseReducerWithPrepare<State, PayloadAction<any, string, any, any>>
|
|---|
| 415 | >
|
|---|
| 416 |
|
|---|
| 417 | /**
|
|---|
| 418 | * The type describing a slice's `selectors` option.
|
|---|
| 419 | */
|
|---|
| 420 | export type SliceSelectors<State> = {
|
|---|
| 421 | [K: string]: (sliceState: State, ...args: any[]) => any
|
|---|
| 422 | }
|
|---|
| 423 |
|
|---|
| 424 | type SliceActionType<
|
|---|
| 425 | SliceName extends string,
|
|---|
| 426 | ActionName extends keyof any,
|
|---|
| 427 | > = ActionName extends string | number ? `${SliceName}/${ActionName}` : string
|
|---|
| 428 |
|
|---|
| 429 | /**
|
|---|
| 430 | * Derives the slice's `actions` property from the `reducers` options
|
|---|
| 431 | *
|
|---|
| 432 | * @public
|
|---|
| 433 | */
|
|---|
| 434 | export type CaseReducerActions<
|
|---|
| 435 | CaseReducers extends SliceCaseReducers<any>,
|
|---|
| 436 | SliceName extends string,
|
|---|
| 437 | > = {
|
|---|
| 438 | [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition
|
|---|
| 439 | ? Definition extends { prepare: any }
|
|---|
| 440 | ? ActionCreatorForCaseReducerWithPrepare<
|
|---|
| 441 | Definition,
|
|---|
| 442 | SliceActionType<SliceName, Type>
|
|---|
| 443 | >
|
|---|
| 444 | : Definition extends AsyncThunkSliceReducerDefinition<
|
|---|
| 445 | any,
|
|---|
| 446 | infer ThunkArg,
|
|---|
| 447 | infer Returned,
|
|---|
| 448 | infer ThunkApiConfig
|
|---|
| 449 | >
|
|---|
| 450 | ? AsyncThunk<Returned, ThunkArg, ThunkApiConfig>
|
|---|
| 451 | : Definition extends { reducer: any }
|
|---|
| 452 | ? ActionCreatorForCaseReducer<
|
|---|
| 453 | Definition['reducer'],
|
|---|
| 454 | SliceActionType<SliceName, Type>
|
|---|
| 455 | >
|
|---|
| 456 | : ActionCreatorForCaseReducer<
|
|---|
| 457 | Definition,
|
|---|
| 458 | SliceActionType<SliceName, Type>
|
|---|
| 459 | >
|
|---|
| 460 | : never
|
|---|
| 461 | }
|
|---|
| 462 |
|
|---|
| 463 | /**
|
|---|
| 464 | * Get a `PayloadActionCreator` type for a passed `CaseReducerWithPrepare`
|
|---|
| 465 | *
|
|---|
| 466 | * @internal
|
|---|
| 467 | */
|
|---|
| 468 | type ActionCreatorForCaseReducerWithPrepare<
|
|---|
| 469 | CR extends { prepare: any },
|
|---|
| 470 | Type extends string,
|
|---|
| 471 | > = _ActionCreatorWithPreparedPayload<CR['prepare'], Type>
|
|---|
| 472 |
|
|---|
| 473 | /**
|
|---|
| 474 | * Get a `PayloadActionCreator` type for a passed `CaseReducer`
|
|---|
| 475 | *
|
|---|
| 476 | * @internal
|
|---|
| 477 | */
|
|---|
| 478 | type ActionCreatorForCaseReducer<CR, Type extends string> = CR extends (
|
|---|
| 479 | state: any,
|
|---|
| 480 | action: infer Action,
|
|---|
| 481 | ) => any
|
|---|
| 482 | ? Action extends { payload: infer P }
|
|---|
| 483 | ? PayloadActionCreator<P, Type>
|
|---|
| 484 | : ActionCreatorWithoutPayload<Type>
|
|---|
| 485 | : ActionCreatorWithoutPayload<Type>
|
|---|
| 486 |
|
|---|
| 487 | /**
|
|---|
| 488 | * Extracts the CaseReducers out of a `reducers` object, even if they are
|
|---|
| 489 | * tested into a `CaseReducerWithPrepare`.
|
|---|
| 490 | *
|
|---|
| 491 | * @internal
|
|---|
| 492 | */
|
|---|
| 493 | type SliceDefinedCaseReducers<CaseReducers extends SliceCaseReducers<any>> = {
|
|---|
| 494 | [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition
|
|---|
| 495 | ? Definition extends AsyncThunkSliceReducerDefinition<any, any, any>
|
|---|
| 496 | ? Id<
|
|---|
| 497 | Pick<
|
|---|
| 498 | Required<Definition>,
|
|---|
| 499 | 'fulfilled' | 'rejected' | 'pending' | 'settled'
|
|---|
| 500 | >
|
|---|
| 501 | >
|
|---|
| 502 | : Definition extends {
|
|---|
| 503 | reducer: infer Reducer
|
|---|
| 504 | }
|
|---|
| 505 | ? Reducer
|
|---|
| 506 | : Definition
|
|---|
| 507 | : never
|
|---|
| 508 | }
|
|---|
| 509 |
|
|---|
| 510 | type RemappedSelector<S extends Selector, NewState> =
|
|---|
| 511 | S extends Selector<any, infer R, infer P>
|
|---|
| 512 | ? Selector<NewState, R, P> & { unwrapped: S }
|
|---|
| 513 | : never
|
|---|
| 514 |
|
|---|
| 515 | /**
|
|---|
| 516 | * Extracts the final selector type from the `selectors` object.
|
|---|
| 517 | *
|
|---|
| 518 | * Removes the `string` index signature from the default value.
|
|---|
| 519 | */
|
|---|
| 520 | type SliceDefinedSelectors<
|
|---|
| 521 | State,
|
|---|
| 522 | Selectors extends SliceSelectors<State>,
|
|---|
| 523 | RootState,
|
|---|
| 524 | > = {
|
|---|
| 525 | [K in keyof Selectors as string extends K ? never : K]: RemappedSelector<
|
|---|
| 526 | Selectors[K],
|
|---|
| 527 | RootState
|
|---|
| 528 | >
|
|---|
| 529 | }
|
|---|
| 530 |
|
|---|
| 531 | /**
|
|---|
| 532 | * Used on a SliceCaseReducers object.
|
|---|
| 533 | * Ensures that if a CaseReducer is a `CaseReducerWithPrepare`, that
|
|---|
| 534 | * the `reducer` and the `prepare` function use the same type of `payload`.
|
|---|
| 535 | *
|
|---|
| 536 | * Might do additional such checks in the future.
|
|---|
| 537 | *
|
|---|
| 538 | * This type is only ever useful if you want to write your own wrapper around
|
|---|
| 539 | * `createSlice`. Please don't use it otherwise!
|
|---|
| 540 | *
|
|---|
| 541 | * @public
|
|---|
| 542 | */
|
|---|
| 543 | export type ValidateSliceCaseReducers<
|
|---|
| 544 | S,
|
|---|
| 545 | ACR extends SliceCaseReducers<S>,
|
|---|
| 546 | > = ACR & {
|
|---|
| 547 | [T in keyof ACR]: ACR[T] extends {
|
|---|
| 548 | reducer(s: S, action?: infer A): any
|
|---|
| 549 | }
|
|---|
| 550 | ? {
|
|---|
| 551 | prepare(...a: never[]): Omit<A, 'type'>
|
|---|
| 552 | }
|
|---|
| 553 | : {}
|
|---|
| 554 | }
|
|---|
| 555 |
|
|---|
| 556 | function getType(slice: string, actionKey: string): string {
|
|---|
| 557 | return `${slice}/${actionKey}`
|
|---|
| 558 | }
|
|---|
| 559 |
|
|---|
| 560 | interface BuildCreateSliceConfig {
|
|---|
| 561 | creators?: {
|
|---|
| 562 | asyncThunk?: typeof asyncThunkCreator
|
|---|
| 563 | }
|
|---|
| 564 | }
|
|---|
| 565 |
|
|---|
| 566 | export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
|
|---|
| 567 | const cAT = creators?.asyncThunk?.[asyncThunkSymbol]
|
|---|
| 568 | return function createSlice<
|
|---|
| 569 | State,
|
|---|
| 570 | CaseReducers extends SliceCaseReducers<State>,
|
|---|
| 571 | Name extends string,
|
|---|
| 572 | Selectors extends SliceSelectors<State>,
|
|---|
| 573 | ReducerPath extends string = Name,
|
|---|
| 574 | >(
|
|---|
| 575 | options: CreateSliceOptions<
|
|---|
| 576 | State,
|
|---|
| 577 | CaseReducers,
|
|---|
| 578 | Name,
|
|---|
| 579 | ReducerPath,
|
|---|
| 580 | Selectors
|
|---|
| 581 | >,
|
|---|
| 582 | ): Slice<State, CaseReducers, Name, ReducerPath, Selectors> {
|
|---|
| 583 | const { name, reducerPath = name as unknown as ReducerPath } = options
|
|---|
| 584 | if (!name) {
|
|---|
| 585 | throw new Error('`name` is a required option for createSlice')
|
|---|
| 586 | }
|
|---|
| 587 |
|
|---|
| 588 | if (
|
|---|
| 589 | typeof process !== 'undefined' &&
|
|---|
| 590 | process.env.NODE_ENV === 'development'
|
|---|
| 591 | ) {
|
|---|
| 592 | if (options.initialState === undefined) {
|
|---|
| 593 | console.error(
|
|---|
| 594 | 'You must provide an `initialState` value that is not `undefined`. You may have misspelled `initialState`',
|
|---|
| 595 | )
|
|---|
| 596 | }
|
|---|
| 597 | }
|
|---|
| 598 |
|
|---|
| 599 | const reducers =
|
|---|
| 600 | (typeof options.reducers === 'function'
|
|---|
| 601 | ? options.reducers(buildReducerCreators<State>())
|
|---|
| 602 | : options.reducers) || {}
|
|---|
| 603 |
|
|---|
| 604 | const reducerNames = Object.keys(reducers)
|
|---|
| 605 |
|
|---|
| 606 | const context: ReducerHandlingContext<State> = {
|
|---|
| 607 | sliceCaseReducersByName: {},
|
|---|
| 608 | sliceCaseReducersByType: {},
|
|---|
| 609 | actionCreators: {},
|
|---|
| 610 | sliceMatchers: [],
|
|---|
| 611 | }
|
|---|
| 612 |
|
|---|
| 613 | const contextMethods: ReducerHandlingContextMethods<State> = {
|
|---|
| 614 | addCase(
|
|---|
| 615 | typeOrActionCreator: string | TypedActionCreator<any>,
|
|---|
| 616 | reducer: CaseReducer<State>,
|
|---|
| 617 | ) {
|
|---|
| 618 | const type =
|
|---|
| 619 | typeof typeOrActionCreator === 'string'
|
|---|
| 620 | ? typeOrActionCreator
|
|---|
| 621 | : typeOrActionCreator.type
|
|---|
| 622 | if (!type) {
|
|---|
| 623 | throw new Error(
|
|---|
| 624 | '`context.addCase` cannot be called with an empty action type',
|
|---|
| 625 | )
|
|---|
| 626 | }
|
|---|
| 627 | if (type in context.sliceCaseReducersByType) {
|
|---|
| 628 | throw new Error(
|
|---|
| 629 | '`context.addCase` cannot be called with two reducers for the same action type: ' +
|
|---|
| 630 | type,
|
|---|
| 631 | )
|
|---|
| 632 | }
|
|---|
| 633 | context.sliceCaseReducersByType[type] = reducer
|
|---|
| 634 | return contextMethods
|
|---|
| 635 | },
|
|---|
| 636 | addMatcher(matcher, reducer) {
|
|---|
| 637 | context.sliceMatchers.push({ matcher, reducer })
|
|---|
| 638 | return contextMethods
|
|---|
| 639 | },
|
|---|
| 640 | exposeAction(name, actionCreator) {
|
|---|
| 641 | context.actionCreators[name] = actionCreator
|
|---|
| 642 | return contextMethods
|
|---|
| 643 | },
|
|---|
| 644 | exposeCaseReducer(name, reducer) {
|
|---|
| 645 | context.sliceCaseReducersByName[name] = reducer
|
|---|
| 646 | return contextMethods
|
|---|
| 647 | },
|
|---|
| 648 | }
|
|---|
| 649 |
|
|---|
| 650 | reducerNames.forEach((reducerName) => {
|
|---|
| 651 | const reducerDefinition = reducers[reducerName]
|
|---|
| 652 | const reducerDetails: ReducerDetails = {
|
|---|
| 653 | reducerName,
|
|---|
| 654 | type: getType(name, reducerName),
|
|---|
| 655 | createNotation: typeof options.reducers === 'function',
|
|---|
| 656 | }
|
|---|
| 657 | if (isAsyncThunkSliceReducerDefinition<State>(reducerDefinition)) {
|
|---|
| 658 | handleThunkCaseReducerDefinition(
|
|---|
| 659 | reducerDetails,
|
|---|
| 660 | reducerDefinition,
|
|---|
| 661 | contextMethods,
|
|---|
| 662 | cAT,
|
|---|
| 663 | )
|
|---|
| 664 | } else {
|
|---|
| 665 | handleNormalReducerDefinition<State>(
|
|---|
| 666 | reducerDetails,
|
|---|
| 667 | reducerDefinition as any,
|
|---|
| 668 | contextMethods,
|
|---|
| 669 | )
|
|---|
| 670 | }
|
|---|
| 671 | })
|
|---|
| 672 |
|
|---|
| 673 | function buildReducer() {
|
|---|
| 674 | if (process.env.NODE_ENV !== 'production') {
|
|---|
| 675 | if (typeof options.extraReducers === 'object') {
|
|---|
| 676 | throw new Error(
|
|---|
| 677 | "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice",
|
|---|
| 678 | )
|
|---|
| 679 | }
|
|---|
| 680 | }
|
|---|
| 681 | const [
|
|---|
| 682 | extraReducers = {},
|
|---|
| 683 | actionMatchers = [],
|
|---|
| 684 | defaultCaseReducer = undefined,
|
|---|
| 685 | ] =
|
|---|
| 686 | typeof options.extraReducers === 'function'
|
|---|
| 687 | ? executeReducerBuilderCallback(options.extraReducers)
|
|---|
| 688 | : [options.extraReducers]
|
|---|
| 689 |
|
|---|
| 690 | const finalCaseReducers = {
|
|---|
| 691 | ...extraReducers,
|
|---|
| 692 | ...context.sliceCaseReducersByType,
|
|---|
| 693 | }
|
|---|
| 694 |
|
|---|
| 695 | return createReducer(options.initialState, (builder) => {
|
|---|
| 696 | for (let key in finalCaseReducers) {
|
|---|
| 697 | builder.addCase(key, finalCaseReducers[key] as CaseReducer<any>)
|
|---|
| 698 | }
|
|---|
| 699 | for (let sM of context.sliceMatchers) {
|
|---|
| 700 | builder.addMatcher(sM.matcher, sM.reducer)
|
|---|
| 701 | }
|
|---|
| 702 | for (let m of actionMatchers) {
|
|---|
| 703 | builder.addMatcher(m.matcher, m.reducer)
|
|---|
| 704 | }
|
|---|
| 705 | if (defaultCaseReducer) {
|
|---|
| 706 | builder.addDefaultCase(defaultCaseReducer)
|
|---|
| 707 | }
|
|---|
| 708 | })
|
|---|
| 709 | }
|
|---|
| 710 |
|
|---|
| 711 | const selectSelf = (state: State) => state
|
|---|
| 712 |
|
|---|
| 713 | const injectedSelectorCache = new Map<
|
|---|
| 714 | boolean,
|
|---|
| 715 | WeakMap<
|
|---|
| 716 | (rootState: any) => State | undefined,
|
|---|
| 717 | Record<string, (rootState: any) => any>
|
|---|
| 718 | >
|
|---|
| 719 | >()
|
|---|
| 720 |
|
|---|
| 721 | const injectedStateCache = new WeakMap<(rootState: any) => State, State>()
|
|---|
| 722 |
|
|---|
| 723 | let _reducer: ReducerWithInitialState<State>
|
|---|
| 724 |
|
|---|
| 725 | function reducer(state: State | undefined, action: UnknownAction) {
|
|---|
| 726 | if (!_reducer) _reducer = buildReducer()
|
|---|
| 727 |
|
|---|
| 728 | return _reducer(state, action)
|
|---|
| 729 | }
|
|---|
| 730 |
|
|---|
| 731 | function getInitialState() {
|
|---|
| 732 | if (!_reducer) _reducer = buildReducer()
|
|---|
| 733 |
|
|---|
| 734 | return _reducer.getInitialState()
|
|---|
| 735 | }
|
|---|
| 736 |
|
|---|
| 737 | function makeSelectorProps<CurrentReducerPath extends string = ReducerPath>(
|
|---|
| 738 | reducerPath: CurrentReducerPath,
|
|---|
| 739 | injected = false,
|
|---|
| 740 | ): Pick<
|
|---|
| 741 | Slice<State, CaseReducers, Name, CurrentReducerPath, Selectors>,
|
|---|
| 742 | 'getSelectors' | 'selectors' | 'selectSlice' | 'reducerPath'
|
|---|
| 743 | > {
|
|---|
| 744 | function selectSlice(state: { [K in CurrentReducerPath]: State }) {
|
|---|
| 745 | let sliceState = state[reducerPath]
|
|---|
| 746 | if (typeof sliceState === 'undefined') {
|
|---|
| 747 | if (injected) {
|
|---|
| 748 | sliceState = getOrInsertComputed(
|
|---|
| 749 | injectedStateCache,
|
|---|
| 750 | selectSlice,
|
|---|
| 751 | getInitialState,
|
|---|
| 752 | )
|
|---|
| 753 | } else if (process.env.NODE_ENV !== 'production') {
|
|---|
| 754 | throw new Error(
|
|---|
| 755 | 'selectSlice returned undefined for an uninjected slice reducer',
|
|---|
| 756 | )
|
|---|
| 757 | }
|
|---|
| 758 | }
|
|---|
| 759 | return sliceState
|
|---|
| 760 | }
|
|---|
| 761 |
|
|---|
| 762 | function getSelectors(
|
|---|
| 763 | selectState: (rootState: any) => State = selectSelf,
|
|---|
| 764 | ) {
|
|---|
| 765 | const selectorCache = getOrInsertComputed(
|
|---|
| 766 | injectedSelectorCache,
|
|---|
| 767 | injected,
|
|---|
| 768 | () => new WeakMap(),
|
|---|
| 769 | )
|
|---|
| 770 |
|
|---|
| 771 | return getOrInsertComputed(selectorCache, selectState, () => {
|
|---|
| 772 | const map: Record<string, Selector<any, any>> = {}
|
|---|
| 773 | for (const [name, selector] of Object.entries(
|
|---|
| 774 | options.selectors ?? {},
|
|---|
| 775 | )) {
|
|---|
| 776 | map[name] = wrapSelector(
|
|---|
| 777 | selector,
|
|---|
| 778 | selectState,
|
|---|
| 779 | () =>
|
|---|
| 780 | getOrInsertComputed(
|
|---|
| 781 | injectedStateCache,
|
|---|
| 782 | selectState,
|
|---|
| 783 | getInitialState,
|
|---|
| 784 | ),
|
|---|
| 785 | injected,
|
|---|
| 786 | )
|
|---|
| 787 | }
|
|---|
| 788 | return map
|
|---|
| 789 | }) as any
|
|---|
| 790 | }
|
|---|
| 791 | return {
|
|---|
| 792 | reducerPath,
|
|---|
| 793 | getSelectors,
|
|---|
| 794 | get selectors() {
|
|---|
| 795 | return getSelectors(selectSlice)
|
|---|
| 796 | },
|
|---|
| 797 | selectSlice,
|
|---|
| 798 | }
|
|---|
| 799 | }
|
|---|
| 800 |
|
|---|
| 801 | const slice: Slice<State, CaseReducers, Name, ReducerPath, Selectors> = {
|
|---|
| 802 | name,
|
|---|
| 803 | reducer,
|
|---|
| 804 | actions: context.actionCreators as any,
|
|---|
| 805 | caseReducers: context.sliceCaseReducersByName as any,
|
|---|
| 806 | getInitialState,
|
|---|
| 807 | ...makeSelectorProps(reducerPath),
|
|---|
| 808 | injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) {
|
|---|
| 809 | const newReducerPath = pathOpt ?? reducerPath
|
|---|
| 810 | injectable.inject({ reducerPath: newReducerPath, reducer }, config)
|
|---|
| 811 | return {
|
|---|
| 812 | ...slice,
|
|---|
| 813 | ...makeSelectorProps(newReducerPath, true),
|
|---|
| 814 | } as any
|
|---|
| 815 | },
|
|---|
| 816 | }
|
|---|
| 817 | return slice
|
|---|
| 818 | }
|
|---|
| 819 | }
|
|---|
| 820 |
|
|---|
| 821 | function wrapSelector<State, NewState, S extends Selector<State>>(
|
|---|
| 822 | selector: S,
|
|---|
| 823 | selectState: Selector<NewState, State>,
|
|---|
| 824 | getInitialState: () => State,
|
|---|
| 825 | injected?: boolean,
|
|---|
| 826 | ) {
|
|---|
| 827 | function wrapper(rootState: NewState, ...args: any[]) {
|
|---|
| 828 | let sliceState = selectState(rootState)
|
|---|
| 829 | if (typeof sliceState === 'undefined') {
|
|---|
| 830 | if (injected) {
|
|---|
| 831 | sliceState = getInitialState()
|
|---|
| 832 | } else if (process.env.NODE_ENV !== 'production') {
|
|---|
| 833 | throw new Error(
|
|---|
| 834 | 'selectState returned undefined for an uninjected slice reducer',
|
|---|
| 835 | )
|
|---|
| 836 | }
|
|---|
| 837 | }
|
|---|
| 838 | return selector(sliceState, ...args)
|
|---|
| 839 | }
|
|---|
| 840 | wrapper.unwrapped = selector
|
|---|
| 841 | return wrapper as RemappedSelector<S, NewState>
|
|---|
| 842 | }
|
|---|
| 843 |
|
|---|
| 844 | /**
|
|---|
| 845 | * A function that accepts an initial state, an object full of reducer
|
|---|
| 846 | * functions, and a "slice name", and automatically generates
|
|---|
| 847 | * action creators and action types that correspond to the
|
|---|
| 848 | * reducers and state.
|
|---|
| 849 | *
|
|---|
| 850 | * @public
|
|---|
| 851 | */
|
|---|
| 852 | export const createSlice = /* @__PURE__ */ buildCreateSlice()
|
|---|
| 853 |
|
|---|
| 854 | interface ReducerHandlingContext<State> {
|
|---|
| 855 | sliceCaseReducersByName: Record<
|
|---|
| 856 | string,
|
|---|
| 857 | | CaseReducer<State, any>
|
|---|
| 858 | | Pick<
|
|---|
| 859 | AsyncThunkSliceReducerDefinition<State, any, any, any>,
|
|---|
| 860 | 'fulfilled' | 'rejected' | 'pending' | 'settled'
|
|---|
| 861 | >
|
|---|
| 862 | >
|
|---|
| 863 | sliceCaseReducersByType: Record<string, CaseReducer<State, any>>
|
|---|
| 864 | sliceMatchers: ActionMatcherDescriptionCollection<State>
|
|---|
| 865 | actionCreators: Record<string, Function>
|
|---|
| 866 | }
|
|---|
| 867 |
|
|---|
| 868 | interface ReducerHandlingContextMethods<State> {
|
|---|
| 869 | /**
|
|---|
| 870 | * Adds a case reducer to handle a single action type.
|
|---|
| 871 | * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type.
|
|---|
| 872 | * @param reducer - The actual case reducer function.
|
|---|
| 873 | */
|
|---|
| 874 | addCase<ActionCreator extends TypedActionCreator<string>>(
|
|---|
| 875 | actionCreator: ActionCreator,
|
|---|
| 876 | reducer: CaseReducer<State, ReturnType<ActionCreator>>,
|
|---|
| 877 | ): ReducerHandlingContextMethods<State>
|
|---|
| 878 | /**
|
|---|
| 879 | * Adds a case reducer to handle a single action type.
|
|---|
| 880 | * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type.
|
|---|
| 881 | * @param reducer - The actual case reducer function.
|
|---|
| 882 | */
|
|---|
| 883 | addCase<Type extends string, A extends Action<Type>>(
|
|---|
| 884 | type: Type,
|
|---|
| 885 | reducer: CaseReducer<State, A>,
|
|---|
| 886 | ): ReducerHandlingContextMethods<State>
|
|---|
| 887 |
|
|---|
| 888 | /**
|
|---|
| 889 | * Allows you to match incoming actions against your own filter function instead of only the `action.type` property.
|
|---|
| 890 | * @remarks
|
|---|
| 891 | * If multiple matcher reducers match, all of them will be executed in the order
|
|---|
| 892 | * they were defined in - even if a case reducer already matched.
|
|---|
| 893 | * All calls to `builder.addMatcher` must come after any calls to `builder.addCase` and before any calls to `builder.addDefaultCase`.
|
|---|
| 894 | * @param matcher - A matcher function. In TypeScript, this should be a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)
|
|---|
| 895 | * function
|
|---|
| 896 | * @param reducer - The actual case reducer function.
|
|---|
| 897 | *
|
|---|
| 898 | */
|
|---|
| 899 | addMatcher<A>(
|
|---|
| 900 | matcher: TypeGuard<A>,
|
|---|
| 901 | reducer: CaseReducer<State, A extends Action ? A : A & Action>,
|
|---|
| 902 | ): ReducerHandlingContextMethods<State>
|
|---|
| 903 | /**
|
|---|
| 904 | * Add an action to be exposed under the final `slice.actions` key.
|
|---|
| 905 | * @param name The key to be exposed as.
|
|---|
| 906 | * @param actionCreator The action to expose.
|
|---|
| 907 | * @example
|
|---|
| 908 | * context.exposeAction("addPost", createAction<Post>("addPost"));
|
|---|
| 909 | *
|
|---|
| 910 | * export const { addPost } = slice.actions
|
|---|
| 911 | *
|
|---|
| 912 | * dispatch(addPost(post))
|
|---|
| 913 | */
|
|---|
| 914 | exposeAction(
|
|---|
| 915 | name: string,
|
|---|
| 916 | actionCreator: Function,
|
|---|
| 917 | ): ReducerHandlingContextMethods<State>
|
|---|
| 918 | /**
|
|---|
| 919 | * Add a case reducer to be exposed under the final `slice.caseReducers` key.
|
|---|
| 920 | * @param name The key to be exposed as.
|
|---|
| 921 | * @param reducer The reducer to expose.
|
|---|
| 922 | * @example
|
|---|
| 923 | * context.exposeCaseReducer("addPost", (state, action: PayloadAction<Post>) => {
|
|---|
| 924 | * state.push(action.payload)
|
|---|
| 925 | * })
|
|---|
| 926 | *
|
|---|
| 927 | * slice.caseReducers.addPost([], addPost(post))
|
|---|
| 928 | */
|
|---|
| 929 | exposeCaseReducer(
|
|---|
| 930 | name: string,
|
|---|
| 931 | reducer:
|
|---|
| 932 | | CaseReducer<State, any>
|
|---|
| 933 | | Pick<
|
|---|
| 934 | AsyncThunkSliceReducerDefinition<State, any, any, any>,
|
|---|
| 935 | 'fulfilled' | 'rejected' | 'pending' | 'settled'
|
|---|
| 936 | >,
|
|---|
| 937 | ): ReducerHandlingContextMethods<State>
|
|---|
| 938 | }
|
|---|
| 939 |
|
|---|
| 940 | interface ReducerDetails {
|
|---|
| 941 | /** The key the reducer was defined under */
|
|---|
| 942 | reducerName: string
|
|---|
| 943 | /** The predefined action type, i.e. `${slice.name}/${reducerName}` */
|
|---|
| 944 | type: string
|
|---|
| 945 | /** Whether create. notation was used when defining reducers */
|
|---|
| 946 | createNotation: boolean
|
|---|
| 947 | }
|
|---|
| 948 |
|
|---|
| 949 | function buildReducerCreators<State>(): ReducerCreators<State> {
|
|---|
| 950 | function asyncThunk(
|
|---|
| 951 | payloadCreator: AsyncThunkPayloadCreator<any, any>,
|
|---|
| 952 | config: AsyncThunkSliceReducerConfig<State, any>,
|
|---|
| 953 | ): AsyncThunkSliceReducerDefinition<State, any> {
|
|---|
| 954 | return {
|
|---|
| 955 | _reducerDefinitionType: ReducerType.asyncThunk,
|
|---|
| 956 | payloadCreator,
|
|---|
| 957 | ...config,
|
|---|
| 958 | }
|
|---|
| 959 | }
|
|---|
| 960 | asyncThunk.withTypes = () => asyncThunk
|
|---|
| 961 | return {
|
|---|
| 962 | reducer(caseReducer: CaseReducer<State, any>) {
|
|---|
| 963 | return Object.assign(
|
|---|
| 964 | {
|
|---|
| 965 | // hack so the wrapping function has the same name as the original
|
|---|
| 966 | // we need to create a wrapper so the `reducerDefinitionType` is not assigned to the original
|
|---|
| 967 | [caseReducer.name](...args: Parameters<typeof caseReducer>) {
|
|---|
| 968 | return caseReducer(...args)
|
|---|
| 969 | },
|
|---|
| 970 | }[caseReducer.name],
|
|---|
| 971 | {
|
|---|
| 972 | _reducerDefinitionType: ReducerType.reducer,
|
|---|
| 973 | } as const,
|
|---|
| 974 | )
|
|---|
| 975 | },
|
|---|
| 976 | preparedReducer(prepare, reducer) {
|
|---|
| 977 | return {
|
|---|
| 978 | _reducerDefinitionType: ReducerType.reducerWithPrepare,
|
|---|
| 979 | prepare,
|
|---|
| 980 | reducer,
|
|---|
| 981 | }
|
|---|
| 982 | },
|
|---|
| 983 | asyncThunk: asyncThunk as any,
|
|---|
| 984 | }
|
|---|
| 985 | }
|
|---|
| 986 |
|
|---|
| 987 | function handleNormalReducerDefinition<State>(
|
|---|
| 988 | { type, reducerName, createNotation }: ReducerDetails,
|
|---|
| 989 | maybeReducerWithPrepare:
|
|---|
| 990 | | CaseReducer<State, { payload: any; type: string }>
|
|---|
| 991 | | CaseReducerWithPrepare<State, PayloadAction<any, string, any, any>>,
|
|---|
| 992 | context: ReducerHandlingContextMethods<State>,
|
|---|
| 993 | ) {
|
|---|
| 994 | let caseReducer: CaseReducer<State, any>
|
|---|
| 995 | let prepareCallback: PrepareAction<any> | undefined
|
|---|
| 996 | if ('reducer' in maybeReducerWithPrepare) {
|
|---|
| 997 | if (
|
|---|
| 998 | createNotation &&
|
|---|
| 999 | !isCaseReducerWithPrepareDefinition(maybeReducerWithPrepare)
|
|---|
| 1000 | ) {
|
|---|
| 1001 | throw new Error(
|
|---|
| 1002 | 'Please use the `create.preparedReducer` notation for prepared action creators with the `create` notation.',
|
|---|
| 1003 | )
|
|---|
| 1004 | }
|
|---|
| 1005 | caseReducer = maybeReducerWithPrepare.reducer
|
|---|
| 1006 | prepareCallback = maybeReducerWithPrepare.prepare
|
|---|
| 1007 | } else {
|
|---|
| 1008 | caseReducer = maybeReducerWithPrepare
|
|---|
| 1009 | }
|
|---|
| 1010 | context
|
|---|
| 1011 | .addCase(type, caseReducer)
|
|---|
| 1012 | .exposeCaseReducer(reducerName, caseReducer)
|
|---|
| 1013 | .exposeAction(
|
|---|
| 1014 | reducerName,
|
|---|
| 1015 | prepareCallback
|
|---|
| 1016 | ? createAction(type, prepareCallback)
|
|---|
| 1017 | : createAction(type),
|
|---|
| 1018 | )
|
|---|
| 1019 | }
|
|---|
| 1020 |
|
|---|
| 1021 | function isAsyncThunkSliceReducerDefinition<State>(
|
|---|
| 1022 | reducerDefinition: any,
|
|---|
| 1023 | ): reducerDefinition is AsyncThunkSliceReducerDefinition<State, any, any, any> {
|
|---|
| 1024 | return reducerDefinition._reducerDefinitionType === ReducerType.asyncThunk
|
|---|
| 1025 | }
|
|---|
| 1026 |
|
|---|
| 1027 | function isCaseReducerWithPrepareDefinition<State>(
|
|---|
| 1028 | reducerDefinition: any,
|
|---|
| 1029 | ): reducerDefinition is CaseReducerWithPrepareDefinition<State, any> {
|
|---|
| 1030 | return (
|
|---|
| 1031 | reducerDefinition._reducerDefinitionType === ReducerType.reducerWithPrepare
|
|---|
| 1032 | )
|
|---|
| 1033 | }
|
|---|
| 1034 |
|
|---|
| 1035 | function handleThunkCaseReducerDefinition<State>(
|
|---|
| 1036 | { type, reducerName }: ReducerDetails,
|
|---|
| 1037 | reducerDefinition: AsyncThunkSliceReducerDefinition<State, any, any, any>,
|
|---|
| 1038 | context: ReducerHandlingContextMethods<State>,
|
|---|
| 1039 | cAT: typeof _createAsyncThunk | undefined,
|
|---|
| 1040 | ) {
|
|---|
| 1041 | if (!cAT) {
|
|---|
| 1042 | throw new Error(
|
|---|
| 1043 | 'Cannot use `create.asyncThunk` in the built-in `createSlice`. ' +
|
|---|
| 1044 | 'Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.',
|
|---|
| 1045 | )
|
|---|
| 1046 | }
|
|---|
| 1047 | const { payloadCreator, fulfilled, pending, rejected, settled, options } =
|
|---|
| 1048 | reducerDefinition
|
|---|
| 1049 | const thunk = cAT(type, payloadCreator, options as any)
|
|---|
| 1050 | context.exposeAction(reducerName, thunk)
|
|---|
| 1051 |
|
|---|
| 1052 | if (fulfilled) {
|
|---|
| 1053 | context.addCase(thunk.fulfilled, fulfilled)
|
|---|
| 1054 | }
|
|---|
| 1055 | if (pending) {
|
|---|
| 1056 | context.addCase(thunk.pending, pending)
|
|---|
| 1057 | }
|
|---|
| 1058 | if (rejected) {
|
|---|
| 1059 | context.addCase(thunk.rejected, rejected)
|
|---|
| 1060 | }
|
|---|
| 1061 | if (settled) {
|
|---|
| 1062 | context.addMatcher(thunk.settled, settled)
|
|---|
| 1063 | }
|
|---|
| 1064 |
|
|---|
| 1065 | context.exposeCaseReducer(reducerName, {
|
|---|
| 1066 | fulfilled: fulfilled || noop,
|
|---|
| 1067 | pending: pending || noop,
|
|---|
| 1068 | rejected: rejected || noop,
|
|---|
| 1069 | settled: settled || noop,
|
|---|
| 1070 | })
|
|---|
| 1071 | }
|
|---|
| 1072 |
|
|---|
| 1073 | function noop() {}
|
|---|