Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent inference of any in create.asyncThunk #4389

Merged
merged 2 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions packages/toolkit/src/createSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,15 +427,7 @@ export interface ReducerCreators<State> {
* @public
*/
export type SliceCaseReducers<State> =
| Record<
string,
| CaseReducerDefinition<State, PayloadAction<any>>
| CaseReducerWithPrepareDefinition<
State,
PayloadAction<any, string, any, any>
>
| AsyncThunkSliceReducerDefinition<State, any, any, any>
>
| Record<string, ReducerDefinition>
| Record<
string,
| CaseReducer<State, PayloadAction<any>>
Expand Down Expand Up @@ -692,7 +684,7 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
} else {
handleNormalReducerDefinition<State>(
reducerDetails,
reducerDefinition,
reducerDefinition as any,
contextMethods,
)
}
Expand Down
38 changes: 38 additions & 0 deletions packages/toolkit/src/tests/createSlice.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,38 @@ describe('type tests', () => {
expectTypeOf(action.error).toEqualTypeOf<'error'>()
},
),
testInferVoid: create.asyncThunk(() => {}, {
pending(state, action) {
expectTypeOf(state).toEqualTypeOf<TestState>()

expectTypeOf(action.meta.arg).toBeVoid()
},
fulfilled(state, action) {
expectTypeOf(state).toEqualTypeOf<TestState>()

expectTypeOf(action.meta.arg).toBeVoid()

expectTypeOf(action.payload).toBeVoid()
},
rejected(state, action) {
expectTypeOf(state).toEqualTypeOf<TestState>()

expectTypeOf(action.meta.arg).toBeVoid()

expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
},
settled(state, action) {
expectTypeOf(state).toEqualTypeOf<TestState>()

expectTypeOf(action.meta.arg).toBeVoid()

if (isRejected(action)) {
expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
} else {
expectTypeOf(action.payload).toBeVoid()
}
},
}),
testInfer: create.asyncThunk(
function payloadCreator(arg: TestArg, api) {
return Promise.resolve<TestReturned>({ payload: 'foo' })
Expand Down Expand Up @@ -856,6 +888,12 @@ describe('type tests', () => {
>
>()

expectTypeOf(slice.actions.testInferVoid).toEqualTypeOf<
AsyncThunk<void, void, {}>
>()

expectTypeOf(slice.actions.testInferVoid).toBeCallableWith()

expectTypeOf(slice.actions.testInfer).toEqualTypeOf<
AsyncThunk<TestReturned, TestArg, {}>
>()
Expand Down
7 changes: 3 additions & 4 deletions packages/toolkit/src/tests/createSlice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ describe('createSlice', () => {
initialState: [] as any[],
reducers: (create) => ({
thunkReducers: create.asyncThunk(
function payloadCreator(arg, api) {
function payloadCreator(arg: string, api) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... This brings up the question: will this still be okay in an untyped .js file for non-TS-users or will it make it unusable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the same behaviour as createAsyncThunk. if they're not using type checking on their JS there will be nothing to complain, and if they are then they'll need to annotate:

const getPost = createAsyncThunk(
  'posts/getPost',
  /**
   *
   * @param {number} id
   * @returns {Promise<Post>}
   */
  async (id) => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts/${id}`,
    )
    const data = await response.json()
    return data
  },
)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can even set the ApiConfig:

const getPost = createAsyncThunk(
  'posts/getPost',
  /** @type {import("@reduxjs/toolkit").AsyncThunkPayloadCreator<Post, number, { state: RootState }>}*/
  async (id, api) => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts/${id}`,
    )
    const data = await response.json()
    return data
  },
)

return Promise.resolve('resolved payload')
},
{ pending, fulfilled, rejected, settled },
Expand Down Expand Up @@ -722,7 +722,7 @@ describe('createSlice', () => {
reducers: (create) => ({
thunkReducers: create.asyncThunk(
// payloadCreator isn't allowed to return never
function payloadCreator(arg, api): any {
function payloadCreator(arg: string, api): any {
throw new Error('')
},
{ pending, fulfilled, rejected, settled },
Expand Down Expand Up @@ -765,7 +765,7 @@ describe('createSlice', () => {
initialState: [] as any[],
reducers: (create) => ({
thunkReducers: create.asyncThunk(
function payloadCreator(arg, api) {
function payloadCreator(arg: string, api) {
return 'should not call this'
},
{
Expand Down Expand Up @@ -833,7 +833,6 @@ describe('createSlice', () => {
slice.actions.thunkReducers.rejected(
new Error('test'),
'fakeRequestId',
{},
),
),
).not.toThrow()
Expand Down