Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import type {
BaseQueryMeta,
BaseQueryResult,
} from '../../baseQueryTypes'
import type { BaseEndpointDefinition } from '../../endpointDefinitions'
import { DefinitionType, isAnyQueryDefinition } from '../../endpointDefinitions'
import type {
BaseEndpointDefinition,
DefinitionType,
} from '../../endpointDefinitions'
import { isAnyQueryDefinition } from '../../endpointDefinitions'
import type { QueryCacheKey, RootState } from '../apiState'
import type {
MutationResultSelectorResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,25 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({
)

const isQueryEnd = isAnyOf(
isFulfilled(mutationThunk, queryThunk),
isRejected(mutationThunk, queryThunk),
isFulfilled(queryThunk, mutationThunk),
isRejected(queryThunk, mutationThunk),
)

let pendingTagInvalidations: FullTagDescription<string>[] = []
// Track via counter so we can avoid iterating over state every time
let pendingRequestCount = 0

const handler: ApiMiddlewareInternalHandler = (action, mwApi) => {
if (
queryThunk.pending.match(action) ||
mutationThunk.pending.match(action)
) {
pendingRequestCount++
}

if (isQueryEnd(action)) {
pendingRequestCount = Math.max(0, pendingRequestCount - 1)
}

if (isThunkActionWithTags(action)) {
invalidateTags(
calculateProvidedByThunk(
Expand Down Expand Up @@ -72,16 +84,8 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({
}
}

function hasPendingRequests(
state: CombinedState<EndpointDefinitions, string, string>,
) {
const { queries, mutations } = state
for (const cacheRecord of [queries, mutations]) {
for (const key in cacheRecord) {
if (cacheRecord[key]?.status === QueryStatus.pending) return true
}
}
return false
function hasPendingRequests() {
return pendingRequestCount > 0
}

function invalidateTags(
Expand All @@ -95,7 +99,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({

if (
state.config.invalidationBehavior === 'delayed' &&
hasPendingRequests(state)
hasPendingRequests()
) {
return
}
Expand Down
55 changes: 26 additions & 29 deletions packages/toolkit/src/query/core/buildSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
TagTypesFrom,
} from '../endpointDefinitions'
import { expandTagDescription } from '../endpointDefinitions'
import { flatten, isNotNullish } from '../utils'
import { filterMap, isNotNullish } from '../utils'
import type {
InfiniteData,
InfiniteQueryConfigOptions,
Expand Down Expand Up @@ -342,7 +342,8 @@ export function buildSelectors<
}> {
const apiState = state[reducerPath]
const toInvalidate = new Set<QueryCacheKey>()
for (const tag of tags.filter(isNotNullish).map(expandTagDescription)) {
const finalTags = filterMap(tags, isNotNullish, expandTagDescription)
for (const tag of finalTags) {
const provided = apiState.provided.tags[tag.type]
if (!provided) {
continue
Expand All @@ -353,27 +354,23 @@ export function buildSelectors<
? // id given: invalidate all queries that provide this type & id
provided[tag.id]
: // no id: invalidate all queries that provide this type
flatten(Object.values(provided))) ?? []
Object.values(provided).flat()) ?? []

for (const invalidate of invalidateSubscriptions) {
toInvalidate.add(invalidate)
}
}

return flatten(
Array.from(toInvalidate.values()).map((queryCacheKey) => {
const querySubState = apiState.queries[queryCacheKey]
return querySubState
? [
{
queryCacheKey,
endpointName: querySubState.endpointName!,
originalArgs: querySubState.originalArgs,
},
]
: []
}),
)
return Array.from(toInvalidate.values()).flatMap((queryCacheKey) => {
const querySubState = apiState.queries[queryCacheKey]
return querySubState
? {
queryCacheKey,
endpointName: querySubState.endpointName!,
originalArgs: querySubState.originalArgs,
}
: []
})
}

function selectCachedArgsForQuery<
Expand All @@ -382,18 +379,18 @@ export function buildSelectors<
state: RootState,
queryName: QueryName,
): Array<QueryArgFromAnyQuery<Definitions[QueryName]>> {
return Object.values(selectQueries(state) as QueryState<any>)
.filter(
(
entry,
): entry is Exclude<
QuerySubState<Definitions[QueryName]>,
{ status: QueryStatus.uninitialized }
> =>
entry?.endpointName === queryName &&
entry.status !== QueryStatus.uninitialized,
)
.map((entry) => entry.originalArgs)
return filterMap(
Object.values(selectQueries(state) as QueryState<any>),
(
entry,
): entry is Exclude<
QuerySubState<Definitions[QueryName]>,
{ status: QueryStatus.uninitialized }
> =>
entry?.endpointName === queryName &&
entry.status !== QueryStatus.uninitialized,
(entry) => entry.originalArgs,
)
}

function getHasNextPage(
Expand Down
5 changes: 3 additions & 2 deletions packages/toolkit/src/query/core/buildSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import type { ApiContext } from '../apiTypes'
import { isUpsertQuery } from './buildInitiate'
import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
import type { UnwrapPromise } from '../tsHelpers'
import { getCurrent } from '../utils/getCurrent'

/**
* A typesafe single entry to be upserted into the cache
Expand Down Expand Up @@ -587,7 +588,7 @@ export function buildSlice({
draft: InvalidationState<any>,
queryCacheKey: QueryCacheKey,
) {
const existingTags = draft.keys[queryCacheKey] ?? []
const existingTags = getCurrent(draft.keys[queryCacheKey] ?? [])

// Delete this cache key from any existing tags that may have provided it
for (const tag of existingTags) {
Expand All @@ -596,7 +597,7 @@ export function buildSlice({
const tagSubscriptions = draft.tags[tagType]?.[tagId]

if (tagSubscriptions) {
draft.tags[tagType][tagId] = tagSubscriptions.filter(
draft.tags[tagType][tagId] = getCurrent(tagSubscriptions).filter(
(qc) => qc !== queryCacheKey,
)
}
Expand Down
26 changes: 14 additions & 12 deletions packages/toolkit/src/query/endpointDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type {
} from './tsHelpers'
import { isNotNullish } from './utils'
import type { NamedSchemaError } from './standardSchema'
import { filterMap } from './utils/filterMap'

const rawResultType = /* @__PURE__ */ Symbol()
const resultType = /* @__PURE__ */ Symbol()
Expand Down Expand Up @@ -1406,20 +1407,21 @@ export function calculateProvidedBy<ResultType, QueryArg, ErrorType, MetaType>(
meta: MetaType | undefined,
assertTagTypes: AssertTagTypes,
): readonly FullTagDescription<string>[] {
if (isFunction(description)) {
return description(
result as ResultType,
error as undefined,
queryArg,
meta as MetaType,
const finalDescription = isFunction(description)
? description(
result as ResultType,
error as undefined,
queryArg,
meta as MetaType,
)
: description

if (finalDescription) {
return filterMap(finalDescription, isNotNullish, (tag) =>
assertTagTypes(expandTagDescription(tag)),
)
.filter(isNotNullish)
.map(expandTagDescription)
.map(assertTagTypes)
}
if (Array.isArray(description)) {
return description.map(expandTagDescription).map(assertTagTypes)
}

return []
}

Expand Down
18 changes: 1 addition & 17 deletions packages/toolkit/src/query/tests/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { vi } from 'vitest'
import {
isOnline,
isDocumentVisible,
flatten,
joinUrls,
} from '@internal/query/utils'
import { isOnline, isDocumentVisible, joinUrls } from '@internal/query/utils'

afterAll(() => {
vi.restoreAllMocks()
Expand Down Expand Up @@ -96,14 +91,3 @@ describe('joinUrls', () => {
expect(joinUrls(base, url)).toBe(expected)
})
})

describe('flatten', () => {
test('flattens an array to a depth of 1', () => {
expect(flatten([1, 2, [3, 4]])).toEqual([1, 2, 3, 4])
})
test('does not flatten to a depth of 2', () => {
const flattenResult = flatten([1, 2, [3, 4, [5, 6]]])
expect(flattenResult).not.toEqual([1, 2, 3, 4, 5, 6])
expect(flattenResult).toEqual([1, 2, 3, 4, [5, 6]])
})
})
27 changes: 27 additions & 0 deletions packages/toolkit/src/query/utils/filterMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Preserve type guard predicate behavior when passing to mapper
export function filterMap<T, U, S extends T = T>(
array: readonly T[],
predicate: (item: T, index: number) => item is S,
mapper: (item: S, index: number) => U | U[],
): U[]

export function filterMap<T, U>(
array: readonly T[],
predicate: (item: T, index: number) => boolean,
mapper: (item: T, index: number) => U | U[],
): U[]

export function filterMap<T, U>(
array: readonly T[],
predicate: (item: T, index: number) => boolean,
mapper: (item: T, index: number) => U | U[],
): U[] {
return array
.reduce<(U | U[])[]>((acc, item, i) => {
if (predicate(item as any, i)) {
acc.push(mapper(item as any, i))
}
return acc
}, [])
.flat() as U[]
}
6 changes: 0 additions & 6 deletions packages/toolkit/src/query/utils/flatten.ts

This file was deleted.

6 changes: 6 additions & 0 deletions packages/toolkit/src/query/utils/getCurrent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Draft } from 'immer'
import { current, isDraft } from 'immer'

export function getCurrent<T>(value: T | Draft<T>): T {
return (isDraft(value) ? current(value) : value) as T
}
2 changes: 1 addition & 1 deletion packages/toolkit/src/query/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from './capitalize'
export * from './copyWithStructuralSharing'
export * from './countObjectKeys'
export * from './flatten'
export * from './filterMap'
export * from './isAbsoluteUrl'
export * from './isDocumentVisible'
export * from './isNotNullish'
Expand Down
2 changes: 1 addition & 1 deletion packages/toolkit/tsup.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export default defineConfig((overrideOptions): TsupOptions[] => {
},
{
...commonOptions,
name: 'Redux-Toolkit-Nexted-Legacy-ESM',
name: 'Redux-Toolkit-Nested-Legacy-ESM',
external: commonOptions.external.concat('@reduxjs/toolkit'),
entry: {
'react/redux-toolkit-react.legacy-esm': 'src/react/index.ts',
Expand Down
Loading