This feature release rewrites RTK Query's internal subscription and polling systems and the useStableQueryArgs
hook for better perf, adds automatic AbortSignal
handling to requests still in progress when a cache entry is removed, fixes a bug with the transformResponse
option for queries, adds a new builder.addAsyncThunk
method, and fixes assorted other issues.
Changelog
RTK Query Performance Improvements
We had reports that RTK Query could get very slow when there were thousands of subscriptions to the same cache entry. After investigation, we found that the internal polling logic was attempting to recalculate the minimum polling time after every new subscription was added. This was highly inefficient, as most subscriptions don't change polling settings, and it required repeated O(n) iteration over the growing list of subscriptions. We've rewritten that logic to debounce the update check and ensure a max of one polling value update per tick for the entire API instance.
Related, while working on the request abort changes, testing showed that use of plain Record
s to hold subscription data was inefficient because we have to iterate keys to check size. We've rewritten the subscription handling internals to use Map
s instead, as well as restructuring some additional checks around in-flight requests.
These two improvements drastically improved runtime perf for the thousands-of-subscriptions-one-cache-entry repro, eliminating RTK methods as visible hotspots in the perf profiles. It likely also improves perf for general usage as well.
We've also changed the implementation of our internal useStableQueryArgs
hook to avoid calling serializeQueryArgs
on its value, which can avoid potential perf issues when a query takes a very large object as its cache key.
Abort Signal Handling on Cleanup
We've had numerous requests over time for various forms of "abort in-progress requests when the data is no longer needed / params change / component unmounts / some expensive request is taking too long". This is a complex topic with multiple potential use cases, and our standard answer has been that we don't want to abort those requests - after all, cache entries default to staying in memory for 1 minute after the last subscription is removed, so RTKQ's cache can still be updated when the request completes. That also means that it doesn't make sense to abort a request "on unmount".
However, it does then make sense to abort an in-progress request if the cache entry itself is removed. Given that, we've updated our cache handling to automatically call the existing resPromise.abort()
method in that case, triggering the AbortSignal
attached to the baseQuery
. The handling at that point depends on your app - fetchBaseQuery
should handle that, a custom baseQuery
or queryFn
would need to listen to the AbortSignal
.
We do have an open issue asking for further discussions of potential abort / cancelation use cases and would appreciate further feedback.
New Options
The builder callback used in createReducer
and createSlice.extraReducers
now has builder.addAsyncThunk
available, which allows handling specific actions from a thunk in the same way that you could define a thunk inside createSlice.reducers
:
const slice = createSlice({
name: 'counter',
initialState: {
loading: false,
errored: false,
value: 0,
},
reducers: {},
extraReducers: (builder) =>
builder.addAsyncThunk(asyncThunk, {
pending(state) {
state.loading = true
},
fulfilled(state, action) {
state.value = action.payload
},
rejected(state) {
state.errored = true
},
settled(state) {
state.loading = false
},
}),
})
createApi
and individual endpoint definitions now accept a skipSchemaValidation
option with an array of schema types to skip, or true
to skip validation entirely (in case you want to use a schema for its types, but the actual validation is expensive).
Bug Fixes
The infinite query implementation accidentally changed the query internals to always run transformResponse
if provided, including if you were using upsertQueryData()
, which then broke. It's been fixed to only run on an actual query request.
The internal changes to the structure of the state.api.provided
structure broke our handling of extractRehydrationInfo
- we've updated that to handle the changed structure.
The infinite query status fields like hasNextPage
are now a looser type of boolean
initially, rather than strictly false
.
TS Types
We now export Immer's WritableDraft
type to fix another non-portable types issue.
We've added an api.endpoints.myEndpoint.types.RawResultType
types-only field to match the other available fields.
What's Changed
- Add RawResultType as a type-only property on endpoints by @EskiMojo14 in #5037
- allow passing an array of specific schemas to skip by @EskiMojo14 in #5042
- fix(types): re-exporting WritableDraft from immer by @marinsokol5 in #5015
- Remove Serialisation from useStableQueryArgs by @riqts in #4996
- add addAsyncThunk method to reducer map builder by @EskiMojo14 in #5007
- Only run
transformResponse
when aquery
is used by @markerikson in #5049 - Assorted bugfixes for 2.8.3 by @markerikson in #5060
- Abort pending requests if the cache entry is removed by @markerikson in #5061
- Update TS CI config by @markerikson in #5065
- Rewrite subscription handling and polling calculations for better perf by @markerikson in #5064
Full Changelog: v2.8.2...v2.9.0