@@ -82,7 +82,8 @@ export type ReducerWithInitialState<S extends NotFunction<any>> = Reducer<S> & {
8282
8383let hasWarnedAboutObjectNotation = false
8484
85- /**
85+ export type CreateReducer = {
86+ /**
8687 * A utility function that allows defining a reducer as a mapping from action
8788 * type to *case reducer* functions that handle these action types. The
8889 * reducer's initial state is passed as the first argument.
@@ -146,90 +147,104 @@ const reducer = createReducer(
146147```
147148 * @public
148149 */
149- export function createReducer < S extends NotFunction < any > > (
150- initialState : S | ( ( ) => S ) ,
151- builderCallback : ( builder : ActionReducerMapBuilder < S > ) => void
152- ) : ReducerWithInitialState < S >
153-
154- export function createReducer < S extends NotFunction < any > > (
155- initialState : S | ( ( ) => S ) ,
156- mapOrBuilderCallback : ( builder : ActionReducerMapBuilder < S > ) => void
157- ) : ReducerWithInitialState < S > {
158- if ( process . env . NODE_ENV !== 'production' ) {
159- if ( typeof mapOrBuilderCallback === 'object' ) {
160- throw new Error (
161- "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer"
162- )
163- }
164- }
150+ < S extends NotFunction < any > > (
151+ initialState : S | ( ( ) => S ) ,
152+ builderCallback : ( builder : ActionReducerMapBuilder < S > ) => void
153+ ) : ReducerWithInitialState < S >
154+ }
155+
156+ export interface BuildCreateReducerConfiguration {
157+ createNextState : < Base > (
158+ base : Base ,
159+ recipe : ( draft : Draft < Base > ) => void | Base | Draft < Base >
160+ ) => Base
161+ }
165162
166- let [ actionsMap , finalActionMatchers , finalDefaultCaseReducer ] =
167- executeReducerBuilderCallback ( mapOrBuilderCallback )
163+ export function buildCreateReducer ( {
164+ createNextState,
165+ } : BuildCreateReducerConfiguration ) : CreateReducer {
166+ return function createReducer < S extends NotFunction < any > > (
167+ initialState : S | ( ( ) => S ) ,
168+ mapOrBuilderCallback : ( builder : ActionReducerMapBuilder < S > ) => void
169+ ) : ReducerWithInitialState < S > {
170+ if ( process . env . NODE_ENV !== 'production' ) {
171+ if ( typeof mapOrBuilderCallback === 'object' ) {
172+ throw new Error (
173+ "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer"
174+ )
175+ }
176+ }
168177
169- // Ensure the initial state gets frozen either way (if draftable)
170- let getInitialState : ( ) => S
171- if ( isStateFunction ( initialState ) ) {
172- getInitialState = ( ) => freezeDraftable ( initialState ( ) )
173- } else {
174- const frozenInitialState = freezeDraftable ( initialState )
175- getInitialState = ( ) => frozenInitialState
176- }
178+ let [ actionsMap , finalActionMatchers , finalDefaultCaseReducer ] =
179+ executeReducerBuilderCallback ( mapOrBuilderCallback )
177180
178- function reducer ( state = getInitialState ( ) , action : any ) : S {
179- let caseReducers = [
180- actionsMap [ action . type ] ,
181- ...finalActionMatchers
182- . filter ( ( { matcher } ) => matcher ( action ) )
183- . map ( ( { reducer } ) => reducer ) ,
184- ]
185- if ( caseReducers . filter ( ( cr ) => ! ! cr ) . length === 0 ) {
186- caseReducers = [ finalDefaultCaseReducer ]
181+ // Ensure the initial state gets frozen either way (if draftable)
182+ let getInitialState : ( ) => S
183+ if ( isStateFunction ( initialState ) ) {
184+ getInitialState = ( ) => freezeDraftable ( initialState ( ) )
185+ } else {
186+ const frozenInitialState = freezeDraftable ( initialState )
187+ getInitialState = ( ) => frozenInitialState
187188 }
188189
189- return caseReducers . reduce ( ( previousState , caseReducer ) : S => {
190- if ( caseReducer ) {
191- if ( isDraft ( previousState ) ) {
192- // If it's already a draft, we must already be inside a `createNextState` call,
193- // likely because this is being wrapped in `createReducer`, `createSlice`, or nested
194- // inside an existing draft. It's safe to just pass the draft to the mutator.
195- const draft = previousState as Draft < S > // We can assume this is already a draft
196- const result = caseReducer ( draft , action )
197-
198- if ( result === undefined ) {
199- return previousState
200- }
190+ function reducer ( state = getInitialState ( ) , action : any ) : S {
191+ let caseReducers = [
192+ actionsMap [ action . type ] ,
193+ ...finalActionMatchers
194+ . filter ( ( { matcher } ) => matcher ( action ) )
195+ . map ( ( { reducer } ) => reducer ) ,
196+ ]
197+ if ( caseReducers . filter ( ( cr ) => ! ! cr ) . length === 0 ) {
198+ caseReducers = [ finalDefaultCaseReducer ]
199+ }
201200
202- return result as S
203- } else if ( ! isDraftable ( previousState ) ) {
204- // If state is not draftable (ex: a primitive, such as 0), we want to directly
205- // return the caseReducer func and not wrap it with produce.
206- const result = caseReducer ( previousState as any , action )
201+ return caseReducers . reduce ( ( previousState , caseReducer ) : S => {
202+ if ( caseReducer ) {
203+ if ( isDraft ( previousState ) ) {
204+ // If it's already a draft, we must already be inside a `createNextState` call,
205+ // likely because this is being wrapped in `createReducer`, `createSlice`, or nested
206+ // inside an existing draft. It's safe to just pass the draft to the mutator.
207+ const draft = previousState as Draft < S > // We can assume this is already a draft
208+ const result = caseReducer ( draft , action )
207209
208- if ( result === undefined ) {
209- if ( previousState === null ) {
210+ if ( result === undefined ) {
210211 return previousState
211212 }
212- throw Error (
213- 'A case reducer on a non-draftable value must not return undefined'
214- )
215- }
216213
217- return result as S
218- } else {
219- // @ts -ignore createNextState() produces an Immutable<Draft<S>> rather
220- // than an Immutable<S>, and TypeScript cannot find out how to reconcile
221- // these two types.
222- return createNextState ( previousState , ( draft : Draft < S > ) => {
223- return caseReducer ( draft , action )
224- } )
214+ return result as S
215+ } else if ( ! isDraftable ( previousState ) ) {
216+ // If state is not draftable (ex: a primitive, such as 0), we want to directly
217+ // return the caseReducer func and not wrap it with produce.
218+ const result = caseReducer ( previousState as any , action )
219+
220+ if ( result === undefined ) {
221+ if ( previousState === null ) {
222+ return previousState
223+ }
224+ throw Error (
225+ 'A case reducer on a non-draftable value must not return undefined'
226+ )
227+ }
228+
229+ return result as S
230+ } else {
231+ // @ts -ignore createNextState() produces an Immutable<Draft<S>> rather
232+ // than an Immutable<S>, and TypeScript cannot find out how to reconcile
233+ // these two types.
234+ return createNextState ( previousState , ( draft : Draft < S > ) => {
235+ return caseReducer ( draft , action )
236+ } )
237+ }
225238 }
226- }
227239
228- return previousState
229- } , state )
230- }
240+ return previousState
241+ } , state )
242+ }
231243
232- reducer . getInitialState = getInitialState
244+ reducer . getInitialState = getInitialState
233245
234- return reducer as ReducerWithInitialState < S >
246+ return reducer as ReducerWithInitialState < S >
247+ }
235248}
249+
250+ export const createReducer = buildCreateReducer ( { createNextState } )
0 commit comments