@@ -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,12 +147,12 @@ 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 >
150+ < S extends NotFunction < any > > (
151+ initialState :S | ( ( ) => S ) ,
152+ builderCallback :( builder :ActionReducerMapBuilder < S > ) => void
153+ ) :ReducerWithInitialState < S >
153154
154- /**
155+ /**
155156 * A utility function that allows defining a reducer as a mapping from action
156157 * type to *case reducer* functions that handle these action types. The
157158 * reducer's initial state is passed as the first argument.
@@ -203,104 +204,118 @@ const counterReducer = createReducer(0, {
203204```
204205 *@public
205206 */
206- export function createReducer <
207- S extends NotFunction < any > ,
208- CR extends CaseReducers < S , any > = CaseReducers < S , any >
209- > (
210- initialState :S | ( ( ) => S ) ,
211- actionsMap :CR ,
212- actionMatchers ?:ActionMatcherDescriptionCollection < S > ,
213- defaultCaseReducer ?:CaseReducer < S >
214- ) :ReducerWithInitialState < S >
215-
216- export function createReducer < S extends NotFunction < any > > (
217- initialState :S | ( ( ) => S ) ,
218- mapOrBuilderCallback :
219- | CaseReducers < S , any >
220- | ( ( builder :ActionReducerMapBuilder < S > ) => void ) ,
221- actionMatchers :ReadonlyActionMatcherDescriptionCollection < S > = [ ] ,
222- defaultCaseReducer ?:CaseReducer < S >
223- ) :ReducerWithInitialState < S > {
224- if ( process . env . NODE_ENV !== 'production' ) {
225- if ( typeof mapOrBuilderCallback === 'object' ) {
226- if ( ! hasWarnedAboutObjectNotation ) {
227- hasWarnedAboutObjectNotation = true
228- console . warn (
229- "The object notation for `createReducer` is deprecated, and will be removed in RTK 2.0. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer"
230- )
207+ <
208+ S extends NotFunction < any > ,
209+ CR extends CaseReducers < S , any > = CaseReducers < S , any >
210+ > (
211+ initialState :S | ( ( ) => S ) ,
212+ actionsMap :CR ,
213+ actionMatchers ?:ActionMatcherDescriptionCollection < S > ,
214+ defaultCaseReducer ?:CaseReducer < S >
215+ ) :ReducerWithInitialState < S >
216+ }
217+
218+ export interface BuildCreateReducerConfiguration {
219+ createNextState :< Base > (
220+ base :Base ,
221+ recipe :( draft :Draft < Base > ) => void | Base | Draft < Base >
222+ ) => Base
223+ }
224+
225+ export function buildCreateReducer ( {
226+ createNextState,
227+ } :BuildCreateReducerConfiguration ) :CreateReducer {
228+ return function createReducer < S extends NotFunction < any > > (
229+ initialState :S | ( ( ) => S ) ,
230+ mapOrBuilderCallback :
231+ | CaseReducers < S , any >
232+ | ( ( builder :ActionReducerMapBuilder < S > ) => void ) ,
233+ actionMatchers :ReadonlyActionMatcherDescriptionCollection < S > = [ ] ,
234+ defaultCaseReducer ?:CaseReducer < S >
235+ ) :ReducerWithInitialState < S > {
236+ if ( process . env . NODE_ENV !== 'production' ) {
237+ if ( typeof mapOrBuilderCallback === 'object' ) {
238+ if ( ! hasWarnedAboutObjectNotation ) {
239+ hasWarnedAboutObjectNotation = true
240+ console . warn (
241+ "The object notation for `createReducer` is deprecated, and will be removed in RTK 2.0. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer"
242+ )
243+ }
231244}
232245}
233- }
234246
235- let [ actionsMap , finalActionMatchers , finalDefaultCaseReducer ] =
236- typeof mapOrBuilderCallback === 'function'
237- ?executeReducerBuilderCallback ( mapOrBuilderCallback )
238- :[ mapOrBuilderCallback , actionMatchers , defaultCaseReducer ]
239-
240- // Ensure the initial state gets frozen either way (if draftable)
241- let getInitialState :( ) => S
242- if ( isStateFunction ( initialState ) ) {
243- getInitialState = ( ) => freezeDraftable ( initialState ( ) )
244- } else {
245- const frozenInitialState = freezeDraftable ( initialState )
246- getInitialState = ( ) => frozenInitialState
247- }
247+ let [ actionsMap , finalActionMatchers , finalDefaultCaseReducer ] =
248+ typeof mapOrBuilderCallback === 'function'
249+ ?executeReducerBuilderCallback ( mapOrBuilderCallback )
250+ :[ mapOrBuilderCallback , actionMatchers , defaultCaseReducer ]
248251
249- function reducer ( state = getInitialState ( ) , action :any ) :S {
250- let caseReducers = [
251- actionsMap [ action . type ] ,
252- ...finalActionMatchers
253- . filter ( ( { matcher} ) => matcher ( action ) )
254- . map ( ( { reducer} ) => reducer ) ,
255- ]
256- if ( caseReducers . filter ( ( cr ) => ! ! cr ) . length === 0 ) {
257- caseReducers = [ finalDefaultCaseReducer ]
252+ // Ensure the initial state gets frozen either way (if draftable)
253+ let getInitialState :( ) => S
254+ if ( isStateFunction ( initialState ) ) {
255+ getInitialState = ( ) => freezeDraftable ( initialState ( ) )
256+ } else {
257+ const frozenInitialState = freezeDraftable ( initialState )
258+ getInitialState = ( ) => frozenInitialState
258259}
259260
260- return caseReducers . reduce ( ( previousState , caseReducer ) :S => {
261- if ( caseReducer ) {
262- if ( isDraft ( previousState ) ) {
263- // If it's already a draft, we must already be inside a `createNextState` call,
264- // likely because this is being wrapped in `createReducer`, `createSlice`, or nested
265- // inside an existing draft. It's safe to just pass the draft to the mutator.
266- const draft = previousState as Draft < S > // We can assume this is already a draft
267- const result = caseReducer ( draft , action )
268-
269- if ( result === undefined ) {
270- return previousState
271- }
261+ function reducer ( state = getInitialState ( ) , action :any ) :S {
262+ let caseReducers = [
263+ actionsMap [ action . type ] ,
264+ ...finalActionMatchers
265+ . filter ( ( { matcher} ) => matcher ( action ) )
266+ . map ( ( { reducer} ) => reducer ) ,
267+ ]
268+ if ( caseReducers . filter ( ( cr ) => ! ! cr ) . length === 0 ) {
269+ caseReducers = [ finalDefaultCaseReducer ]
270+ }
272271
273- return result as S
274- } else if ( ! isDraftable ( previousState ) ) {
275- // If state is not draftable (ex: a primitive, such as 0), we want to directly
276- // return the caseReducer func and not wrap it with produce.
277- const result = caseReducer ( previousState as any , action )
272+ return caseReducers . reduce ( ( previousState , caseReducer ) :S => {
273+ if ( caseReducer ) {
274+ if ( isDraft ( previousState ) ) {
275+ // If it's already a draft, we must already be inside a `createNextState` call,
276+ // likely because this is being wrapped in `createReducer`, `createSlice`, or nested
277+ // inside an existing draft. It's safe to just pass the draft to the mutator.
278+ const draft = previousState as Draft < S > // We can assume this is already a draft
279+ const result = caseReducer ( draft , action )
278280
279- if ( result === undefined ) {
280- if ( previousState === null ) {
281+ if ( result === undefined ) {
281282return previousState
282283}
283- throw Error (
284- 'A case reducer on a non-draftable value must not return undefined'
285- )
286- }
287284
288- return result as S
289- } else {
290- //@ts -ignore createNextState() produces an Immutable<Draft<S>> rather
291- // than an Immutable<S>, and TypeScript cannot find out how to reconcile
292- // these two types.
293- return createNextState ( previousState , ( draft :Draft < S > ) => {
294- return caseReducer ( draft , action )
295- } )
285+ return result as S
286+ } else if ( ! isDraftable ( previousState ) ) {
287+ // If state is not draftable (ex: a primitive, such as 0), we want to directly
288+ // return the caseReducer func and not wrap it with produce.
289+ const result = caseReducer ( previousState as any , action )
290+
291+ if ( result === undefined ) {
292+ if ( previousState === null ) {
293+ return previousState
294+ }
295+ throw Error (
296+ 'A case reducer on a non-draftable value must not return undefined'
297+ )
298+ }
299+
300+ return result as S
301+ } else {
302+ //@ts -ignore createNextState() produces an Immutable<Draft<S>> rather
303+ // than an Immutable<S>, and TypeScript cannot find out how to reconcile
304+ // these two types.
305+ return createNextState ( previousState , ( draft :Draft < S > ) => {
306+ return caseReducer ( draft , action )
307+ } )
308+ }
296309}
297- }
298310
299- return previousState
300- } , state )
301- }
311+ return previousState
312+ } , state )
313+ }
302314
303- reducer . getInitialState = getInitialState
315+ reducer . getInitialState = getInitialState
304316
305- return reducer as ReducerWithInitialState < S >
317+ return reducer as ReducerWithInitialState < S >
318+ }
306319}
320+
321+ export const createReducer = buildCreateReducer ( { createNextState} )