import type { AnyAction, Reducer } from 'redux' import { createNextState } from '.' import type { ActionCreatorWithoutPayload, PayloadAction, PayloadActionCreator, PrepareAction, _ActionCreatorWithPreparedPayload, } from './createAction' import { createAction } from './createAction' import type { CaseReducer, CaseReducers, ReducerWithInitialState, } from './createReducer' import { createReducer, NotFunction } from './createReducer' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' import type { NoInfer } from './tsHelpers' import { freezeDraftable } from './utils' let hasWarnedAboutObjectNotation = false /** * An action creator attached to a slice. * * @deprecated please use PayloadActionCreator directly * * @public */ export type SliceActionCreator

= PayloadActionCreator

/** * The return value of `createSlice` * * @public */ export interface Slice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, Name extends string = string > { /** * The slice name. */ name: Name /** * The slice's reducer. */ reducer: Reducer /** * Action creators for the types of actions that are handled by the slice * reducer. */ actions: CaseReducerActions /** * The individual case reducer functions that were passed in the `reducers` parameter. * This enables reuse and testing if they were defined inline when calling `createSlice`. */ caseReducers: SliceDefinedCaseReducers /** * Provides access to the initial state value given to the slice. * If a lazy state initializer was provided, it will be called and a fresh value returned. */ getInitialState: () => State } /** * Options for `createSlice()`. * * @public */ export interface CreateSliceOptions< State = any, CR extends SliceCaseReducers = SliceCaseReducers, Name extends string = string > { /** * The slice's name. Used to namespace the generated action types. */ name: Name /** * 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`. */ initialState: State | (() => State) /** * A mapping from action types to action-type-specific *case reducer* * functions. For every action type, a matching action creator will be * generated using `createAction()`. */ reducers: ValidateSliceCaseReducers /** * A callback that receives a *builder* object to define * case reducers via calls to `builder.addCase(actionCreatorOrType, reducer)`. * * Alternatively, a mapping from action types to action-type-specific *case reducer* * functions. These reducers should have existing action types used * as the keys, and action creators will _not_ be generated. * * @example ```ts import { createAction, createSlice, Action, AnyAction } from '@reduxjs/toolkit' const incrementBy = createAction('incrementBy') const decrement = createAction('decrement') interface RejectedAction extends Action { error: Error } function isRejectedAction(action: AnyAction): action is RejectedAction { return action.type.endsWith('rejected') } createSlice({ name: 'counter', initialState: 0, reducers: {}, extraReducers: builder => { builder .addCase(incrementBy, (state, action) => { // action is inferred correctly here if using TS }) // You can chain calls, or have separate `builder.addCase()` lines each time .addCase(decrement, (state, action) => {}) // You can match a range of action types .addMatcher( isRejectedAction, // `action` will be inferred as a RejectedAction due to isRejectedAction being defined as a type guard (state, action) => {} ) // and provide a default case if no other handlers matched .addDefaultCase((state, action) => {}) } }) ``` */ extraReducers?: | CaseReducers, any> | ((builder: ActionReducerMapBuilder>) => void) } /** * A CaseReducer with a `prepare` method. * * @public */ export type CaseReducerWithPrepare = { reducer: CaseReducer prepare: PrepareAction } /** * The type describing a slice's `reducers` option. * * @public */ export type SliceCaseReducers = { [K: string]: | CaseReducer> | CaseReducerWithPrepare> } type SliceActionType< SliceName extends string, ActionName extends keyof any > = ActionName extends string | number ? `${SliceName}/${ActionName}` : string /** * Derives the slice's `actions` property from the `reducers` options * * @public */ export type CaseReducerActions< CaseReducers extends SliceCaseReducers, SliceName extends string > = { [Type in keyof CaseReducers]: CaseReducers[Type] extends { prepare: any } ? ActionCreatorForCaseReducerWithPrepare< CaseReducers[Type], SliceActionType > : ActionCreatorForCaseReducer< CaseReducers[Type], SliceActionType > } /** * Get a `PayloadActionCreator` type for a passed `CaseReducerWithPrepare` * * @internal */ type ActionCreatorForCaseReducerWithPrepare< CR extends { prepare: any }, Type extends string > = _ActionCreatorWithPreparedPayload /** * Get a `PayloadActionCreator` type for a passed `CaseReducer` * * @internal */ type ActionCreatorForCaseReducer = CR extends ( state: any, action: infer Action ) => any ? Action extends { payload: infer P } ? PayloadActionCreator : ActionCreatorWithoutPayload : ActionCreatorWithoutPayload /** * Extracts the CaseReducers out of a `reducers` object, even if they are * tested into a `CaseReducerWithPrepare`. * * @internal */ type SliceDefinedCaseReducers> = { [Type in keyof CaseReducers]: CaseReducers[Type] extends { reducer: infer Reducer } ? Reducer : CaseReducers[Type] } /** * Used on a SliceCaseReducers object. * Ensures that if a CaseReducer is a `CaseReducerWithPrepare`, that * the `reducer` and the `prepare` function use the same type of `payload`. * * Might do additional such checks in the future. * * This type is only ever useful if you want to write your own wrapper around * `createSlice`. Please don't use it otherwise! * * @public */ export type ValidateSliceCaseReducers< S, ACR extends SliceCaseReducers > = ACR & { [T in keyof ACR]: ACR[T] extends { reducer(s: S, action?: infer A): any } ? { prepare(...a: never[]): Omit } : {} } function getType(slice: string, actionKey: string): string { return `${slice}/${actionKey}` } /** * A function that accepts an initial state, an object full of reducer * functions, and a "slice name", and automatically generates * action creators and action types that correspond to the * reducers and state. * * The `reducer` argument is passed to `createReducer()`. * * @public */ export function createSlice< State, CaseReducers extends SliceCaseReducers, Name extends string = string >( options: CreateSliceOptions ): Slice { const { name } = options if (!name) { throw new Error('`name` is a required option for createSlice') } if ( typeof process !== 'undefined' && process.env.NODE_ENV === 'development' ) { if (options.initialState === undefined) { console.error( 'You must provide an `initialState` value that is not `undefined`. You may have misspelled `initialState`' ) } } const initialState = typeof options.initialState == 'function' ? options.initialState : freezeDraftable(options.initialState) const reducers = options.reducers || {} const reducerNames = Object.keys(reducers) const sliceCaseReducersByName: Record = {} const sliceCaseReducersByType: Record = {} const actionCreators: Record = {} reducerNames.forEach((reducerName) => { const maybeReducerWithPrepare = reducers[reducerName] const type = getType(name, reducerName) let caseReducer: CaseReducer let prepareCallback: PrepareAction | undefined if ('reducer' in maybeReducerWithPrepare) { caseReducer = maybeReducerWithPrepare.reducer prepareCallback = maybeReducerWithPrepare.prepare } else { caseReducer = maybeReducerWithPrepare } sliceCaseReducersByName[reducerName] = caseReducer sliceCaseReducersByType[type] = caseReducer actionCreators[reducerName] = prepareCallback ? createAction(type, prepareCallback) : createAction(type) }) function buildReducer() { if (process.env.NODE_ENV !== 'production') { if (typeof options.extraReducers === 'object') { if (!hasWarnedAboutObjectNotation) { hasWarnedAboutObjectNotation = true console.warn( "The object notation for `createSlice.extraReducers` is deprecated, and will be removed in RTK 2.0. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice" ) } } } const [ extraReducers = {}, actionMatchers = [], defaultCaseReducer = undefined, ] = typeof options.extraReducers === 'function' ? executeReducerBuilderCallback(options.extraReducers) : [options.extraReducers] const finalCaseReducers = { ...extraReducers, ...sliceCaseReducersByType } return createReducer(initialState, (builder) => { for (let key in finalCaseReducers) { builder.addCase(key, finalCaseReducers[key] as CaseReducer) } for (let m of actionMatchers) { builder.addMatcher(m.matcher, m.reducer) } if (defaultCaseReducer) { builder.addDefaultCase(defaultCaseReducer) } }) } let _reducer: ReducerWithInitialState return { name, reducer(state, action) { if (!_reducer) _reducer = buildReducer() return _reducer(state, action) }, actions: actionCreators as any, caseReducers: sliceCaseReducersByName as any, getInitialState() { if (!_reducer) _reducer = buildReducer() return _reducer.getInitialState() }, } }