Skip to content

Releases: reduxjs/redux-toolkit

v1.9.0-rc.0

30 Oct 23:24
Compare
Choose a tag to compare
v1.9.0-rc.0 Pre-release
Pre-release

This release candidate includes a new "auto-batching" store enhancer, support for passing some global options to fetchBaseQuery, a fix for forceRefetch behavior, and internal tweaks to checks for missing RTKQ middleware setup.

Please try this out and give us feedback (even if it's just "tried updating and everything's fine")! If no further issues come up we intend to publish 1.9 in the next few days.

npm i @reduxjs/toolkit@next

yarn add @reduxjs/toolkit@next

Docs updates for 1.9 are complete, and can be viewed here:

https://deploy-preview-2401--redux-starter-kit-docs.netlify.app/

Changelog

New Auto-Batching Store Enhancer

There are several different ways to "batch actions" with Redux stores, ranging from reducers to debounced subscriber notifications.

RTK now includes a new autoBatchEnhancer() store enhancer that uses a variation on the "debounced notification" approach, inspired by React's technique of batching renders and determining if an update is low-priority or high-priority.

The enhancer looks for any actions tagged with an action.meta[SHOULD_AUTOBATCH] = true flag, and delays notifying subscribers until the end of the event loop tick. This means that if multiple "auto-batched" actions are dispatched in a row, there will be only one subscriber notification. However, if any "normal-priority" action without that flag is dispatched in the same tick, the enhancer will notify subscribers immediately.

This allows Redux users to selectively tag certain actions for effective batching behavior, making this purely opt-in on a per-action basis, while retaining normal notification behavior for all other actions.

RTK Query's internals have been updated to mark several key actions as batchable. While the enhancer is purely opt-in, benchmarks indicate that it can help speed up UI performance with RTK Query, especially when rendering many components with query hooks. We recommend adding it to your store setup:

  const store = configureStore({
  reducer,
  enhancers: (existingEnhancers) => {
    // Add the autobatch enhancer to the store setup
    return existingEnhancers.concat(autoBatchEnhancer())
  },
})

Additionally, there's a prepareAutoBatched util that can be used to help add the SHOULD_AUTOBATCH flag to actions, designed for use with createSlice:

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 } as CounterState,
  reducers: {
    incrementBatched: {
      // Batched, low-priority
      reducer(state) {
        state.value += 1
      },
      // Use the `prepareAutoBatched` utility to automatically
      // add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs
      prepare: prepareAutoBatched<void>(),
    },
    // Not batched, normal priority
    decrementUnbatched(state) {
      state.value -= 1
    },
  },
})

fetchBaseQuery Global Options

fetchBaseQuery now supports passing the responseHandler, validateStatus, and timeout options directly to fetchBaseQuery itself, in addition to accepting it as part of specific endpoints. If provided, these options will be applied as defaults to all requests for that API, which simplifies using them on many endpoints.

Other Changes

Providing serializeQueryArgs and forceRefetch options for an endpoint now works correctly when you pass an object as the cache key argument to a query hook.

The defaultSerializeQueryArgs util is now exported.

The endpoint-specific serializeQueryArgs option now allows returning an object or a number instead of just a string. If a string is returned, it will be used as-is for the serialized cache key. If an object or number is returned, that value will be passed to defaultSerializeQueryArgs. This simplifies the common case of wanting to remove a couple fields from the cache key, without needing to call defaultSerializeQueryArgs yourself.

Internal tweaks to the RTKQ middleware behavior for detecting cases where the middleware has not been added to the store.

The API docs for the 1.9 preview are fully updated.

What's Changed

  • Fill out v1.9 remaining docs by @markerikson in #2804
  • Rework endpoint serializeQueryArgs to allow object/number returns by @markerikson in #2835
  • allow for global responseHandler and validateStatus configuration by @phryneas in #2823
  • Fix refetches when sQA returns same value and queryArgs are object by @markerikson in #2844
  • Add an auto-batching enhancer that delays low-pri notifications and use with RTKQ by @markerikson in #2846
  • Check middleware registration directly to avoid persistence issues by @markerikson in #2850

Full Changelog: v1.9.0-beta.0...v1.9.0-rc.0

v1.9.0-beta.0

19 Oct 13:53
Compare
Choose a tag to compare
v1.9.0-beta.0 Pre-release
Pre-release

This feature-complete preview release includes several new options for RTK Query API endpoints as well as the ability to "upsert" data, performance improvements to the RTK Query middleware, improvements to fetchBaseQuery behavior, several TS types tweaks and additional type exports, a runtime deprecation warning for the object argument form of createReducer and createSlice.extraReducers, and codemods to update those methods.

Please try this out and give us feedback (even if it's just "tried updating and everything's fine")! We believe that all changes intended for 1.9 are stable and should work, but as always we'd appreciate real-world checks to confirm that.

npm i @reduxjs/toolkit@next

yarn add @reduxjs/toolkit@next

Docs updates for 1.9 are in progress and can be viewed here:

https://deploy-preview-2401--redux-starter-kit-docs.netlify.app/

Changes Since Alpha

Codemods for Deprecated Object Reducer Syntax

Per the description in 1.9.0-alpha.0, we plan to remove the "object" argument from createReducer and createSlice.extraReducers in the future RTK 2.0 major version. In 1.9.0-alpha.0, we added a one-shot runtime warning to each of those APIs.

To simplify upgrading codebases, we've published a set of codemods that will automatically transform the deprecated "object" syntax into the equivalent "builder" syntax.

The codemods package is available on NPM as @reduxjs/rtk-codemods. It currently contains two codemods: createReducerBuilder and createSliceBuilder.

To run the codemods against your codebase, run npx @reduxjs/rtk-codemods <TRANSFORM NAME> path/of/files/ or/some**/*glob.js.

Examples:

npx @reduxjs/rtk-codemods createReducerBuilder ./src

npx @reduxjs/rtk-codemods createSliceBuilder ./packages/my-app/**/*.ts

We also recommend re-running Prettier on the codebase before committing the changes.

These codemods should work, but we would greatly appreciate testing and feedback on more real-world codebases!

Before:

createReducer(initialState, {
  [todoAdded1a]: (state, action) => {
    // stuff
  },
  [todoAdded1b]: (state, action) => action.payload,
});

const slice1 = createSlice({
  name: "a",
  initialState: {},
  extraReducers: {
    [todoAdded1a]: (state, action) => {
      // stuff
    },
    [todoAdded1b]: (state, action) => action.payload,
  }
})

After:

createReducer(initialState, (builder) => {
  builder.addCase(todoAdded1a, (state, action) => {
    // stuff
  });

  builder.addCase(todoAdded1b, (state, action) => action.payload);
})

const slice1 = createSlice({
  name: "a",
  initialState: {},

  extraReducers: (builder) => {
    builder.addCase(todoAdded1a, (state, action) => {
      // stuff
    });

    builder.addCase(todoAdded1b, (state, action) => action.payload);
  }
})

getRunningOperationPromises Deprecation and Replacement

In v1.7.0, we added an api.util.getRunningOperationPromises() method for use with SSR scenarios, as well as a singular getRunningOperationPromise() method intended for possible use with React Suspense.

Unfortunately, in #2477 we realized that both those methods have a fatal flaw - they do not work with multiple stores in SSR.

As of this release, we are immediately marking getRunningOperationPromises() as deprecated and discouraging its use before we remove it completely in RTK 2.0! It will now throw both runtime and compile errors in development to enforce moving away from using it. However, we are leaving its existing behavior in production builds to avoid actual breakage.

The getRunningOperationPromise() util was experimental, and as far as we can tell not actually being used by anyone, so we are removing getRunningOperationPromise completely in this release.

As replacements, RTKQ now includes four new thunks attached to api.util:

  • getRunningQueryThunk(endpointName, queryArgs)
  • getRunningMutationThunk(endpointName, fixedCacheKeyOrRequestId)
  • getRunningQueriesThunk()
  • getRunningMutationsThunk()

Usages would typically change like this:

-await Promise.all(api.util.getRunningOperationPromises())
+await Promise.all(dispatch(api.util.getRunningQueriesThunk()))

Summary of Alpha Changes

See the previous release notes for more details.

RTK Query Options

  • API endpoints:
    • serializeQueryArgs option ( #2493 )
    • merge option ( #1059 )
    • forceRefetch option ( #2663 )
    • transformErrorResponse option ( #1841
  • upsertQueryData util ( #2266 )
  • RTKQ middleware optimization ( #2641, #2759 )
  • fetchBaseQuery options:
    • jsonContentType ( #2403 )
    • timeout ( #2363 )
    • content-type response handler ( #2363 )
    • prepareHeaders can return void ( #2775 )
  • hook triggers return promises ( #2212 )
  • retry conditions by response ( #2239 )

TS Types

  • action types are now template literals ( #2250 )
  • Store enhancer types ( #2550 )
  • createAsyncThunk.withTypes ( #2604 )
  • Several additional exported types:

TS Support Matrix

Redux Toolkit now requires TypeScript 4.2 or higher, and works correctly with TS 4.8.

v1.9.0-alpha.2

09 Oct 02:02
Compare
Choose a tag to compare
v1.9.0-alpha.2 Pre-release
Pre-release

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 to QueryExtraOptions 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

09 Oct 01:44
Compare
Choose a tag to compare

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

28 Aug 03:50
Compare
Choose a tag to compare
v1.9.0-alpha.1 Pre-release
Pre-release

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

Full Changelog: v1.9.0-alpha.0...v1.9.0-alpha.1

v1.9.0-alpha.0

19 Aug 21:41
Compare
Choose a tag to compare
v1.9.0-alpha.0 Pre-release
Pre-release

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

Full Changelog: v1.8.5...v1.9.0-alpha.0

v1.8.5

19 Aug 21:13
Compare
Choose a tag to compare

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 overflowing setTimeout 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

11 Aug 01:49
Compare
Choose a tag to compare

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

30 Jun 04:24
Compare
Choose a tag to compare

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

New Contributors

Full Changelog: v1.8.2...1.8.3

v1.8.2

25 May 01:01
Compare
Choose a tag to compare

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

Full Changelog: v1.8.1...v1.8.2