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

Fix manually initiate()d rtk-query promises #2187

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
23 changes: 17 additions & 6 deletions packages/toolkit/src/query/core/buildInitiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DefinitionType } from '../endpointDefinitions'
import type { QueryThunk, MutationThunk } from './buildThunks'
import type { AnyAction, ThunkAction, SerializedError } from '@reduxjs/toolkit'
import type { SubscriptionOptions, RootState } from './apiState'
import { QueryStatus } from './apiState'
import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
import type { Api, ApiContext } from '../apiTypes'
import type { ApiEndpointQuery } from './module'
Expand Down Expand Up @@ -274,17 +275,27 @@ Features like automatic cache collection, automatic refetching etc. will not be
originalArgs: arg,
queryCacheKey,
})
const selector = (
api.endpoints[endpointName] as ApiEndpointQuery<any, any>
).select(arg)

const thunkResult = dispatch(thunk)
const stateAfter = selector(getState())

middlewareWarning(getState)

const { requestId, abort } = thunkResult

const skippedSynchronously = stateAfter.requestId !== requestId

const runningQuery = runningQueries[queryCacheKey]

const statePromise: QueryActionCreatorResult<any> = Object.assign(
Promise.all([runningQueries[queryCacheKey], thunkResult]).then(() =>
(api.endpoints[endpointName] as ApiEndpointQuery<any, any>).select(
arg
)(getState())
),
skippedSynchronously && !runningQuery
? Promise.resolve(stateAfter)
: Promise.all([runningQuery, thunkResult]).then(() =>
selector(getState())
),
{
arg,
requestId,
Expand Down Expand Up @@ -328,7 +339,7 @@ Features like automatic cache collection, automatic refetching etc. will not be
}
)

if (!runningQueries[queryCacheKey]) {
if (!runningQuery && !skippedSynchronously) {
runningQueries[queryCacheKey] = statePromise
statePromise.then(() => {
delete runningQueries[queryCacheKey]
Expand Down
54 changes: 54 additions & 0 deletions packages/toolkit/src/query/tests/buildInitiate.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createApi } from '../core'
import { fakeBaseQuery } from '../fakeBaseQuery'
import { setupApiStore } from './helpers'

let calls = 0
const api = createApi({
baseQuery: fakeBaseQuery(),
endpoints: (build) => ({
increment: build.query<number, void>({
async queryFn() {
const data = calls++
await Promise.resolve()
return { data }
},
}),
}),
})

const storeRef = setupApiStore(api)

test('multiple synchonrous initiate calls with pre-existing cache entry', async () => {
const { store, api } = storeRef
// seed the store
const firstValue = await store.dispatch(api.endpoints.increment.initiate())

expect(firstValue).toMatchObject({ data: 0, status: 'fulfilled' })

// dispatch another increment
const secondValuePromise = store.dispatch(api.endpoints.increment.initiate())
// and one with a forced refresh
const thirdValuePromise = store.dispatch(
api.endpoints.increment.initiate(undefined, { forceRefetch: true })
)
// and another increment
const fourthValuePromise = store.dispatch(api.endpoints.increment.initiate())

const secondValue = await secondValuePromise
const thirdValue = await thirdValuePromise
const fourthValue = await fourthValuePromise

expect(secondValue).toMatchObject({
data: firstValue.data,
status: 'fulfilled',
requestId: firstValue.requestId,
})

expect(thirdValue).toMatchObject({ data: 1, status: 'fulfilled' })
expect(thirdValue.requestId).not.toBe(firstValue.requestId)
expect(fourthValue).toMatchObject({
data: thirdValue.data,
status: 'fulfilled',
requestId: thirdValue.requestId,
})
})