Repository: reduxjs/redux-toolkit
Pull request #756 (2020-10-08 18:00:51)

Allow CaseReducers to also return `Draft<T>`
To help in situations where `T` contains an unresolved generic.
Thanks to @amcsi for reporting this and providing a reproduction
for this issue.

netlify[bot]: Deploy preview for *redux-starter-kit-docs* ready!

Built with commit 076b810e0f2acb0b8f92cba8800c8c988218a0ea

https://deploy-preview-756--redux-starter-kit-docs.netlify.app
codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/reduxjs/redux-toolkit/pr/756/builds/61250) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 392cf50f461a61e3a5d789039e65ab01f9293d2c:

|Sandbox| Source |
|--|--|
|[Vanilla](https://codesandbox.io/s/vanilla-wl9vb)| Configuration |
|[Vanilla Typescript](https://codesandbox.io/s/vanilla-typescript-mdd0x)| Configuration |
|[rsk-github-issues-example](https://codesandbox.io/s/rsk-github-issues-example-93wd7)| Configuration |

markerikson: Fine by me.  If you're happy with it, merge it.

Patch:
diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md
index 7c712c32..36429e42 100644
--- a/etc/redux-toolkit.api.md
+++ b/etc/redux-toolkit.api.md
@@ -90,7 +90,7 @@ export type AsyncThunkPayloadCreator<Returned, ThunkArg = void, ThunkApiConfig e
 export type AsyncThunkPayloadCreatorReturnValue<Returned, ThunkApiConfig extends AsyncThunkConfig> = Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>> | Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>;
 
 // @public
-export type CaseReducer<S = any, A extends Action = AnyAction> = (state: Draft<S>, action: A) => S | void;
+export type CaseReducer<S = any, A extends Action = AnyAction> = (state: Draft<S>, action: A) => S | void | Draft<S>;
 
 // @public
 export type CaseReducerActions<CaseReducers extends SliceCaseReducers<any>> = {
diff --git a/src/createReducer.ts b/src/createReducer.ts
index fe7512d8..d06c71f8 100644
--- a/src/createReducer.ts
+++ b/src/createReducer.ts
@@ -48,7 +48,7 @@ export type ActionMatcherDescriptionCollection<S> = Array<
 export type CaseReducer<S = any, A extends Action = AnyAction> = (
   state: Draft<S>,
   action: A
-) => S | void
+) => S | void | Draft<S>
 
 /**
  * A mapping from action types to case reducers for `createReducer()`.
@@ -223,7 +223,7 @@ export function createReducer<S>(
             return previousState
           }
 
-          return result
+          return result as S
         } else if (!isDraftable(previousState)) {
           // If state is not draftable (ex: a primitive, such as 0), we want to directly
           // return the caseReducer func and not wrap it with produce.
@@ -238,7 +238,7 @@ export function createReducer<S>(
             )
           }
 
-          return result
+          return result as S
         } else {
           // @ts-ignore createNextState() produces an Immutable<Draft<S>> rather
           // than an Immutable<S>, and TypeScript cannot find out how to reconcile


Test patch:
diff --git a/type-tests/files/createSlice.typetest.ts b/type-tests/files/createSlice.typetest.ts
index ea459e1c..5e6c03a4 100644
--- a/type-tests/files/createSlice.typetest.ts
+++ b/type-tests/files/createSlice.typetest.ts
@@ -460,3 +460,39 @@ const value = actionCreators.anyKey
   expectType<ActionCreatorWithPayload<string>>(wrappedSlice.actions.success)
   expectType<ActionCreatorWithoutPayload<string>>(wrappedSlice.actions.magic)
 }
+
+{
+  interface GenericState<T> {
+    data: T | null
+  }
+
+  function createDataSlice<
+    T,
+    Reducers extends SliceCaseReducers<GenericState<T>>
+  >(
+    name: string,
+    reducers: ValidateSliceCaseReducers<GenericState<T>, Reducers>,
+    initialState: GenericState<T>
+  ) {
+    const doNothing = createAction<undefined>('doNothing')
+    const setData = createAction<T>('setData')
+
+    const slice = createSlice({
+      name,
+      initialState,
+      reducers,
+      extraReducers: builder => {
+        builder.addCase(doNothing, state => {
+          return { ...state }
+        })
+        builder.addCase(setData, (state, { payload }) => {
+          return {
+            ...state,
+            data: payload
+          }
+        })
+      }
+    })
+    return { doNothing, setData, slice }
+  }
+}