Releases: reduxjs/redux-toolkit
v1.9.0-alpha.2
This feature preview release fixes broken behavior in the new upsertQueryData
API, fixes behavior with the serializeQueryArgs+merge
combination by adding a new forceRefetch
option, rewrites the internal subscription tracking to speed up mount/update times, adds new TS type exports, and includes the bug fixes from 1.8.6.
Changelog
upsertQueryData
Fix
We released the new upsertQueryData
util in 1.9.0-alpha.1
, but the behavior didn't actually work as intended in some cases. We've tweaked the implementation and it now should work correctly. Please try it out and let us know!
New forceRefresh
option and Infinite Loading Usages
In earlier alphas, we added a new serializeQueryArgs
endpoint option to allow customization of cache keys. This serves two purposes: leaving out values that are passed in to an endpoint but not really part of the "key" conceptually (like a socket or client instance), and altering cache key behavior to use a single entry for the endpoint such as in an infinite loading / pagination scenario.
Along with that, we also added a merge
option that lets you modify the contents of an existing cache entry when new data is received, instead always replacing it.
Due to the existing internal logic, these two options are insufficient for the infinite loading / pagination case. For example, changing useGetItemsQuery(pageNum)
from 1
to 2
starts the checks for fetching new data, but it bails out internally because there's already a cache entry that is status: fulfilled
for that endpoint and cache key.
We've added an additional forceRefresh
call back that receives {currentArg, previousArg, state, endpointState}
as arguments. Use this in combination with the other options to force this endpoint to refetch when args change despite a single cache entry already existing, such as this example:
forceRefetch({currentArg, previousArg}) {
// Assume these are page numbers
return currentArg !== previousArg
},
serializeQueryArgs({endpointName}) {
// Will only have one cache entry for this endpoint, because of the consistent name
return endpointName
},
merge(currentCacheData, responseData) {
// Always add incoming data to the existing cache entry
currentCacheData.push(...responseData)
}
The forceRefresh
option may also be useful in other app-specific scenarios as well.
Internal Subscription Logic Rewrite
RTKQ tracks subscription entries, and thus far has done so by saving them in the Redux state and updating that by dispatching actions as each query hook loads. We've seen that this can be a performance bottleneck in edge cases where hundreds of query hooks are being mounted at once. (This shouldn't be a concern in most apps, but some users were stress-testing things :) )
We've reworked the internal RTKQ middleware logic to track most of the subscription data inside the middleware and sync it to the Redux store at the end of an event loop tick. This should speed up those large list edge cases by cutting 90% of the startup time.
What's Changed
- fix
upsertQueryData
race situations by @phryneas in #2646 - Fix upsert by @phryneas in #2669
- Export the BaseQueryApi interface from rtk-query so it can be used as a type in TypeScript without importing from dist/. by @nsthorat in #2740
- Fix retryCondition
error
. by @phryneas in #2743 - fix: export ToolkitStore interface from configureStore by @adamhari in #2750
- Fix the
dispatch
type inference to correctly handle read-only middleware arrays by @dokmic in #2629 - fix(toolkit): export "ThunkMiddleware" from redux-thunk by @VinceOPS in #2745
- Remove previous api tags before adding new provided tags by @Bezmehrabi in #2702
- reset
dispatchQueued
variable after flushing by @phryneas in #2757 - Fix invalidateTags by @manceau-jb in #2721
- Add
forceRefetch
toQueryExtraOptions
by @schadenn in #2663 - Speed up subscription behavior by tracking state in middleware by @markerikson in #2759
Full Changelog: v1.9.0-alpha.1...v1.9.0-alpha.2
v1.8.6
This bugfix release fixes a couple of issues with RTKQ endpoint tags not invalidating correctly, and tweaks the dispatch
type inference to handle more variations of arrays.
What's Changed
- Fix the
dispatch
type inference to correctly handle read-only middleware arrays by @dokmic in #2629 - fix(toolkit): export "ThunkMiddleware" from redux-thunk by @VinceOPS in #2745
- Remove previous api tags before adding new provided tags by @Bezmehrabi in #2702
- Fix invalidateTags by @manceau-jb in #2721
Full Changelog: v1.8.5...v1.8.6
v1.9.0-alpha.1
This feature preview release adds new options and improves behavior for RTK Query.
As of this alpha, RTK 1.9 is feature-complete and we do not expect additional meaningful changes. The remaining work is filling out docs and preparing the "object reducer" codemods for release alongside 1.9.
Still no specific ETA for release, but we hope for Soon (TM) :)
Changelog
upsertQueryData
API
RTKQ already has an updateQueryData
util to synchronously modify the contents of an existing cache entry, but there was no way to create a new cache entry and its metadata programmatically.
This release adds a new api.util.upsertQueryData
API that allows creating a cache entry + its data programmatically. As with the other util methods, this is a thunk that should be dispatched, and you should pass in the exact cache key arg and complete data value you want to insert:
dispatch(
api.util.upsertQueryData('post', '3', {
id: '3',
title: 'All about cheese.',
contents: 'I love cheese!',
})
)
RTKQ Middleware Performance Optimizations
RTKQ automatically generates a Redux middleware for each "API slice" / createApi
call. That middleware itself has been made up of 7 individual middleware internally composed into the final middleware, with each individual middleware responsible for a different task like managing polling or cache lifetimes.
While this worked well for encapsulating responsibilities, it also meant that every dispatched Redux action had to go through 7 additional middleware. This added multiple function calls to each dispatch's stack trace, and even if there isn't a noticeable perf hit, it wasn't efficient. We've also had some reports that users with multiple API slices were occasionally seeing "Maximum call stack size exceeded" errors.
We've rewritten the internals of the middleware to be more efficient. Actions now only receive extra processing if they're actually related to the API slice itself, and that processing is done via a loop over handling logic at the end of the middleware unwind phase, rather than nesting function calls through multiple sub-middleware. That should keep the call stack shorter in the cases where API actions really are being processed.
Other RTKQ Updates
The refetch()
methods now return a promise that can be awaited.
Query endpoints can now accept a retryCondition
callback as an alternative to maxRetries
. If you provide retryCondition
, it will be called to determine if RTKQ should retry a failed request again.
What's Changed
- return promise from query result & hook
refetch
by @phryneas in #2212 - retry condition by http error response by @kahirokunn in #2239
- implemented upsertQueryData functionality per #1720 #2007 by @barnabasJ in #2266
- Consolidate RTKQ middleware to simplify stack size by @markerikson in #2641
- Add TS 4.8 to the test matrix by @markerikson in #2642
- Try fixing createAsyncThunk issues with TS 4.8 by @markerikson in #2643
- Fix assorted issues with the RTKQ middleware refactor by @markerikson in #2644
Full Changelog: v1.9.0-alpha.0...v1.9.0-alpha.1
v1.9.0-alpha.0
This feature preview release adds new options and improves behavior for RTK Query, adds a runtime deprecation for the "object" form of createReducer/createSlice.extraReducers
, adds the ability to define a "pre-typed" version of createAsyncThunk
, improves TS inference of store enhancers, and exports additional TS types.
We hope to add a couple additional RTKQ options as part of the final 1.9 release, including an upsertQueryData
util. See the RTK 1.9 milestone for remaining items. No hard ETA yet, but ideally we'd like to publish 1.9 within the next couple weeks if we can wrap things up.
Notable Changes
Some highlights for this alpha:
RTKQ merge
Option
RTKQ was built around the assumption that the server is the source of truth, and every refetch replaces the cached data on the client. There are use cases when it would be useful to merge an incoming response into the existing cached data instead, such as pagination or APIs that return varying results over time.
Query endpoints can now accept a merge(cachedData, responseData)
callback that lets you do Immer-powered "mutations" to update the existing cached data instead of replacing it entirely.
Object Reducer Deprecation Warning
RTK's createReducer
API was originally designed to accept a lookup table of action type strings to case reducers, like { "ADD_TODO" : (state, action) => {} }
. We later added the "builder callback" form to allow more flexibility in adding "matchers" and a default handler, and did the same for createSlice.extraReducers
.
We intend to remove the "object" form for both createReducer
and createSlice.extraReducers
in RTK 2.0. The builder callback form is effectively the same number of lines of code, and works much better with TypeScript.
Starting with this release, RTK will print a one-time runtime warning for both createReducer
and createSlice.extraReducers
if you pass in an object argument.
As an example, this:
const todoAdded = createAction('todos/todoAdded');
createReducer(initialState, {
[todoAdded]: (state, action) => {}
})
createSlice({
name,
initialState,
reducers: {/* case reducers here */},
extraReducers: {
[todoAdded]: (state, action) => {}
}
})
should be migrated to:
createReducer(initialState, builder => {
builder.addCase(todoAdded, (state, action) => {})
})
createSlice({
name,
initialState,
reducers: {/* case reducers here */},
extraReducers: builder => {
builder.addCase(todoAdded, (state, action) => {})
}
})
We have initial codemods in the repo that will help rewrite the object form to the builder form, and we'll publish those with instructions alongside 1.9 when it goes final.
RTKQ Internal Improvements
When query hooks mount, they dispatch actions to subscribe to the relevant data. The first hook to do so will dispatch a "subscription/fulfilled" action, and all further hooks asking for the same cache key will dispatch "subscription/rejected" actions. Both cause the reducer logic to add another entry to the subscription tracking.
The dispatching of individual "subscription/rejected" actions was causing perf issues when many components mounted at once, due to the number of extra dispatches. RTKQ now batches those into a single combined action per event loop tick, which improves perf for some many-component use cases noticeably.
TS Types
configureStore
now correctly infers changes to the store shape from any store enhancers.
There's now a createAsyncThunk.withTypes()
method that can be used to create a "pre-typed" version of createAsyncThunk
with types like {state, dispatch, extra}
baked in.
What's Changed
- Add
isJsonContentType
predicate tofetchBaseQuery
by @msutkowski in #2331 - Add
jsonContentType
tofetchBaseQuery
options by @msutkowski in #2403 - Add ThunkMiddleware to the re-exported types from redux-thunk by @orta in #2451
- add timeout option to fetchBaseQuery by @phryneas in #2143
- createSlice: use template literal types for action type by @phryneas in #2250
- add types for manually typing hook results in userland code by @phryneas in #2276
- Add 'content-type' ResponseHandler by @taylorkline in #2363
- feature : endpoint-specific args serializers by @michal-kurz in #2493
- RFC: add "merge" functionality by @phryneas in #1059
- Add runtime deprecation warning for reducer object notation by @markerikson in #2591
- feat: Add the ability to type
StoreEnhancers
by @fostyfost in #2550 - Batch RTKQ "subscribe on reject" actions, and improve cache collection timer handling by @markerikson in #2599
- add transformErrorReponse to rtkq endpoints by @dreyks in #1841
- Fix manually initiate()d rtk-query promises by @wesen in #2187
- Implement codemods for createReducer and createSlice builder by @markerikson in #2602
- RFC: Expose endpoint types by @phryneas in #1646
- add createAsyncThunk.withTypes by @phryneas in #2604
Full Changelog: v1.8.5...v1.9.0-alpha.0
v1.8.5
This bugfix releas fixes an issue with large keepUnusedDataFor
values overflowing JS timers, exports the types for the Redux DevTools Extension option, and and improves behavior of URL string generation.
Changelog
keepUnusedDataFor
Timer Fix
keepUnusedDataFor
accepts a value in seconds. When there are no more active subscriptions for a piece of data, RTKQ will set a timer using setTimeout
, and keepUnusedDataFor * 1000
as the timer value.
We've been advising users that if they want to keep data in the cache forever that they should use a very large value for keepUnusedDataFor
, such as 10 years in seconds.
However, it turns out that JS engines use a 32-bit signed int for timers, and 32-bits in milliseconds is only 24.8 days. If a timer is given a value larger than that, it triggers immediately.
We've updated the internal logic to clamp the keepUnusedDataFor
value to be between 0 and THIRTY_TWO_BIT_MAX_TIMER_SECONDS - 1
.
Note that in RTK 1.9 (coming soon), RTKQ will also accept Infinity
as a special keepUnusedDataFor
value to indicate cached data should never be expired.
Other Changes
RTK inlines the TS types for the Redux DevTools Extension options to avoid an extra dependency, but the TS type for the options object wasn't exported publicly. We now export the DevToolsEnhancerOptions
type.
The logic for generating a final URL has been updated to avoid adding an extra trailing /
.
What's Changed
- Prevent
keepUnusedDataFor
values from overflowingsetTimeout
counter by @markerikson in #2595 - remove typeof undefined checks where not necessary 🐃🪒 by @phryneas in #1726
- Update RDT options types, and export those + AnyListenerPredicate by @markerikson in #2596
- Ensures there is no unexpected slash in url before query params by @ygrishajev in #2470
Full Changelog: v1.8.4...v1.8.5
v1.8.4
This bugfix release adds exported TS types for RTKQ hooks for use in wrapping logic, adds useDebugValue
to the hooks to improve display in the React DevTools, updates the inlined types for the Redux DevTools options, and fixes an issue in createEntityAdapter
that could result in duplicate IDs being stored.
Changelog
RTKQ Hook Result Types
RTK's types heavily rely on inference to minimize the amount of type info users have to provide. However, this can also make it difficult to write functions that wrap calls to RTK APIs.
Some users have asked to have types that help them write "higher-order hooks". RTK now exports types that represent "the return object for a query/mutation hook with a given value": TypedUseQueryHookResult
and TypedUseMutationResult
. Both require <ResultType, QueryArg, BaseQuery>
as generics, like this:
const baseQuery = fetchBaseQuery({url: "https://some.server"});
type CustomHookResult = TypedUseQueryHookResult<MyResultObject, MyArgObject, typeof baseQuery>
const useMyCustomHook = (arg: MyArgObject) : CustomHookResult => {
return api.useGetSomeDataQuery(arg);
}
Redux DevTools Options Fixes
As of Redux DevTools 3.0, some of field names for custom DevTools options have changed to actionsAllowlist
and actionsDenylist
. Since we inline the types instead of having a separate dependency, we've updated our TS types to match that. No runtime behavior was changed.
Other Changes
RTKQ hooks now use useDebugValue
to give a better preview of the current value in the React DevTools "Component" tab.
The <ApiProvider>
component now does a better job of registering and cleaning up focus listeners.
Fixed a bug with createEntityAdapter
that could allow duplicate IDs to be added depending on update parameters.
What's Changed
- fix: prevent duplicate ids when updating id of an element with the id of an existing element by @samatar26 in #2020
- ApiProvider: correctly register listeners, allow to disable listeners by @phryneas in #2277
- Update devtools options TS types based on RDT 3.0 by @markerikson in #2480
- fix: typo in unhandled error message by @MichielTondeur in #2538
- Use useDebugValue in useQuery and useMutation hooks by @kaankeskin in #2500
- add types for manually typing hook results in userland code by @markerikson in #2580
Full Changelog: v1.8.3...v1.8.4
v1.8.3
This bugfix release fixes a few minor issues and bits of behavior, including updating the React-Redux peer dep to ^8.0.2
final, stable sorting in createEntityAdapter.updateMany
and some initial state handling in createSlice
.
Changelog
React-Redux Peer Dep
We'd previously published an RTK build that accepted React-Redux v8 beta as a peer dep (for use with RTK Query). Since React-Redux v8 is out now, we've updated the peer dep to ^8.0.2
.
Entity Adapter Updates
Previously, applying updates via createEntityAdapter.updateMany
caused sorting order to change. Entities that had the same sorting result should have stayed in the same order relative to each other, but if one of those items had any updates, it would sort to the back of that group. This was due to items being removed from the lookup table and re-added, and since JS engines iterate keys in insertion order, the updated item would now end up compared later than before.
We've reworked the implementation of updateMany
to avoid that. This also ended up fixing another issue where multiple update entries targeting the same item ID would only have the first applied.
createSlice
Initial State
createSlice
now logs an error if initialState
is undefined
. This is most commonly seen when users misspell initialState
. It also has better handling for values that can't be frozen by Immer such as primitives.
RTK Query
Several assorted improvements, including TS types for BaseQuery
and checking if the body can actually be safely stringified.
What's Changed
- Add Missing Else to enhanceEndpoints Function by @kinson in #2386
- Check initial state is draftable before using immer to freeze it. by @EskiMojo14 in #2378
- Check that body isJsonifiable before stringify by @ShaunDychko in #2330
- Respect BaseQuery meta types when enhancing by @TamasSzigeti in #2225
- Throw new error when initial state is undefined by @dannielss in #2461
- Rewrite
updateMany
to ensure stable sorting order by @markerikson in #2464 - Bump React-Redux peer dep by @markerikson in 3033a33
New Contributors
Full Changelog: v1.8.2...1.8.3
v1.8.2
This bugfix release fixes a minor issue where calling listenerMiddleware.startListening()
multiple times with the same effect
callback reference would result in multiple entries being added. The correct behavior is that only the first entry is added, and later attempts to add the same effect callback reference just return the existing entry.
What's Changed
- Add type @remarks for configureStore's middleware by @msutkowski in #2252
- Fix the "map values transpilation" bug, in yet another place by @markerikson in #2351
Full Changelog: v1.8.1...v1.8.2
v1.8.1
This release updates RTK's peer dependencies to accept React 18 as a valid version. This should fix installation errors caused by NPM's "install all the peer deps and error if they don't match" behavior.
React-Redux and React 18
Note: If you are now using React 18, we strongly recommend using the React-Redux v8 beta instead of v7.x!. v8 has been rewritten internally to work correctly with React 18's Concurrent Rendering capabilities. React-Redux v7 will run and generally work okay with existing code, but may have rendering issues if you start using Concurrent Rendering capabilities in your code.
Now that React 18 is out, we plan to finalize React-Redux v8 and release it live within the next couple weeks. We would really appreciate final feedback on using React-Redux v8 beta with React 18 before we publish the final version.
v1.8.0
This release adds the new "listener" middleware, updates configureStore
's types to better handle type inference from middleware that override dispatch
return values, and updates our TS support matrix to drop support for TS < 4.1.
Changelog
New "Listener" Side Effects Middleware
RTK has integrated the thunk middleware since the beginning. However, thunks are imperative functions, and do not let you run code in response to dispatched actions. That use case has typically been covered with libraries like redux-saga
(which handles side effects with "sagas" based on generator functions), redux-observable
(which uses RxJS observables), or custom middleware.
We've added a new "listener" middleware to RTK to cover that use case. The listener middleware is created using createListenerMiddleware()
, and lets you define "listener" entries that contain an "effect" callback with additional logic and a way to specify when that callback should run based on dispatched actions or state changes.
Conceptually, you can think of this as being similar to React's useEffect
hook, except that it runs logic in response to Redux store updates instead of component props/state updates.
The listener middleware is intended to be a lightweight alternative to more widely used Redux async middleware like sagas and observables. While similar to thunks in level of complexity and concept, it can replicate some common saga usage patterns. We believe that the listener middleware can be used to replace most of the remaining use cases for sagas, but with a fraction of the bundle size and a much simpler API.
Listener effect callbacks have access to dispatch
and getState
, similar to thunks. The listener also receives a set of async workflow functions like take
, condition
, pause
, fork
, and unsubscribe
, which allow writing more complex async logic.
Listeners can be defined statically by calling listenerMiddleware.startListening()
during setup, or added and removed dynamically at runtime with special dispatch(addListener())
and dispatch(removeListener())
actions.
The API reference is available at:
https://redux-toolkit.js.org/api/createListenerMiddleware
Huge thanks to @FaberVitale for major contributions in refining the middleware API and implementing key functionality.
Basic usage of the listener middleware looks like:
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit'
import todosReducer, {
todoAdded,
todoToggled,
todoDeleted,
} from '../features/todos/todosSlice'
// Create the middleware instance and methods
const listenerMiddleware = createListenerMiddleware()
// Add one or more listener entries that look for specific actions.
// They may contain any sync or async logic, similar to thunks.
listenerMiddleware.startListening({
actionCreator: todoAdded,
effect: async (action, listenerApi) => {
// Run whatever additional side-effect-y logic you want here
console.log('Todo added: ', action.payload.text)
// Can cancel other running instances
listenerApi.cancelActiveListeners()
// Run async logic
const data = await fetchData()
// Pause until action dispatched or state changed
if (await listenerApi.condition(matchSomeAction)) {
// Use the listener API methods to dispatch, get state,
// unsubscribe the listener, start child tasks, and more
listenerApi.dispatch(todoAdded('Buy pet food'))
listenerApi.unsubscribe()
}
},
})
const store = configureStore({
reducer: {
todos: todosReducer,
},
// Add the listener middleware to the store.
// NOTE: Since this can receive actions with functions inside,
// it should go before the serializability check middleware
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().prepend(listenerMiddleware.middleware),
})
You can use it to write more complex async workflows, including pausing the effect callback until a condition check resolves, and forking "child tasks" to do additional work:
// Track how many times each message was processed by the loop
const receivedMessages = {
a: 0,
b: 0,
c: 0,
}
const eventPollingStarted = createAction('serverPolling/started')
const eventPollingStopped = createAction('serverPolling/stopped')
listenerMiddleware.startListening({
actionCreator: eventPollingStarted,
effect: async (action, listenerApi) => {
// Only allow one instance of this listener to run at a time
listenerApi.unsubscribe()
// Start a child job that will infinitely loop receiving messages
const pollingTask = listenerApi.fork(async (forkApi) => {
try {
while (true) {
// Cancellation-aware pause for a new server message
const serverEvent = await forkApi.pause(pollForEvent())
// Process the message. In this case, just count the times we've seen this message.
if (serverEvent.type in receivedMessages) {
receivedMessages[
serverEvent.type as keyof typeof receivedMessages
]++
}
}
} catch (err) {
if (err instanceof TaskAbortError) {
// could do something here to track that the task was cancelled
}
}
})
// Wait for the "stop polling" action
await listenerApi.condition(eventPollingStopped.match)
pollingTask.cancel()
},
})
configureStore
Middleware Type Improvements
Middleware can override the default return value of dispatch
. configureStore
tries to extract any declared dispatch
type overrides from the middleware
array, and uses that to alter the type of store.dispatch
.
We identified some cases where the type inference wasn't working well enough, and rewrote the type behavior to be more correct.
TypeScript Support Matrix Updates
RTK now requires TS 4.1 or greater to work correctly, and we've dropped 4.0 and earlier from our support matrix.
Other Changes
The internal logic for the serializability middleware has been reorganized to allow skipping checks against actions, while still checking values in the state.
What's Changed
Since most of the implementation work on the middleware was done over the last few months, this list only contains the most recent PRs since 1.7.2. For details on the original use case discussions and the evolution of the middleware API over time, see:
- RTK issue #237: Add an action listener middleware
- RTK PR #547: yet another attempt at an action listener middleware
- RTK discussion #1648: New experimental "action listener middleware" package available
PRs since 1.7.2:
- Rewrite MiddlewareArray and gDM for better Dispatch inference by @markerikson in #2001
- Change listener middleware API name and signature by @markerikson in #2005
- feat(alm): add cancellation message to TaskAbortError, listenerApi.signal & forkApi.signal. by @FaberVitale in #2023
- [fix][1.8.0-integration][alm]: missing type export by @FaberVitale in #2026
- [chore][1.8.0-integration][alm]: apply alm breaking API changes to counter-example by @FaberVitale in #2025
- Reorganize serializable check conditions by @msutkowski in #2000
- fix(alm): prevent zombie listeners caused by forked tasks by @FaberVitale in #2070
- Integrate the listener middleware into the RTK package by @markerikson in #2072
- fix(alm): cancel forkApi.delay and forkApi.pause if listener is cancelled or completed by @markerikson in #2074
- chore(alm): update counter example to 1.8.0 by @FaberVitale in #2076
- Enable cancelling active listeners when unsubscribing by @markerikson in #2078
- v1.8.0 integration by @markerikson in #2024
Full Changelog: v1.7.2...v1.8.0