Skip to content

Releases: reduxjs/redux-toolkit

v2.0.0-alpha.6

16 May 14:18
Compare
Choose a tag to compare
v2.0.0-alpha.6 Pre-release
Pre-release

This is an alpha release for Redux Toolkit 2.0, and has breaking changes. This release updates createSlice to allow declaring thunks directly inside the reducers field using a callback syntax, adds a new "dynamic middleware" middleware, updates configureStore to add the autoBatchEnhancer by default, removes the .toString() override from action creators, updates Reselect from v4.x to v5.0-alpha, updates the Redux core to v5.0-alpha.6, and includes the latest changes from 1.9.x.

npm i @reduxjs/toolkit@alpha

yarn add @reduxjs/toolkit@alpha

The 2.0 integration branch contains the docs preview for the 2.0 changes. Not all changes are documented yet, but you can see API reference pages for some of the new features here:

Changelog

Declaring Thunks Inside createSlice.reducers

One of the oldest feature requests we've had is the ability to declare thunks directly inside of createSlice. Until now, you've always had to declare them separately, give the thunk a string action prefix, and handle the actions via createSlice.extraReducers:

// Declare the thunk separately
const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId: number, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

const usersSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: (builder) => {
    // Add reducers for additional action types here, and handle loading state as needed
    builder.addCase(fetchUserById.fulfilled, (state, action) => {
      state.entities.push(action.payload)
    })
  },
})

Many users have told us that this separation feels awkward.

We've wanted to include a way to define thunks directly inside of createSlice, and have played around with various prototypes. There were always two major blocking issues, and a secondary concern:

1 It wasn't clear what the syntax for declaring a thunk inside should look like.
2. Thunks have access to getState and dispatch, but the RootState and AppDispatch types are normally inferred from the store, which in turn infers it from the slice state types. Declaring thunks inside createSlice would cause circular type inference errors, as the store needs the slice types but the slice needs the store types. We weren't willing to ship an API that would work okay for our JS users but not for our TS users, especially since we want people to use TS with RTK.
3. You can't do synchronous conditional imports in ES modules, and there's no good way to make the createAsyncThunk import optional. Either createSlice always depends on it (and adds that to the bundle size), or it can't use createAsyncThunk at all.

We've settled on these compromises:

  • You can declare thunks inside of createSlice.reducers, by using a "creator callback" syntax for the reducers field that is similar to the build callback syntax in RTK Query's createApi (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the reducers field, but is still fairly similar.
  • You can customize some of the types for thunks inside of createSlice, but you cannot customize the state or dispatch types. If those are needed, you can manually do an as cast, like getState() as RootState.
  • createSlice does now always depend on createAsyncThunk, so the createAsyncThunk implementation will get added to the bundle.

In practice, we hope these are reasonable tradeoffs. Creating thunks inside of createSlice has been widely asked for, so we think it's an API that will see usage. If the TS customization options are a limitation, you can still declare thunks outside of createSlice as always, and most async thunks don't need dispatch or getState - they just fetch data and return. And finally, createAsyncThunk is already being used in many apps, either directly or as part of RTK Query, so in that case there's no additional bundle size increase - you've already paid that cost.

Here's what the new callback syntax looks like:

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    loading: false,
    todos: [],
  } as TodoState,
  reducers: (create) => ({
    // A normal "case reducer", same as always
    deleteTodo: create.reducer((state, action: PayloadAction<number>) => {
      state.todos.splice(action.payload, 1)
    }),
    // A case reducer with a "prepare callback" to customize the action
    addTodo: create.preparedReducer(
      (text: string) => {
        const id = nanoid()
        return { payload: { id, text } }
      },
      // action type is inferred from prepare callback
      (state, action) => {
        state.todos.push(action.payload)
      }
    ),
    // An async thunk
    fetchTodo: create.asyncThunk(
      // Async payload function as the first argument
      async (id: string, thunkApi) => {
        const res = await fetch(`myApi/todos?id=${id}`)
        return (await res.json()) as Item
      },
      // An object containing `{pending?, rejected?, fulfilled?, options?}` second
      {
        pending: (state) => {
          state.loading = true
        },
        rejected: (state, action) => {
          state.loading = false
        },
        fulfilled: (state, action) => {
          state.loading = false
          state.todos.push(action.payload)
        },
      }
    ),
  }),
})

// `addTodo` and `deleteTodo` are normal action creators.
// `fetchTodo` is the async thunk
export const { addTodo, deleteTodo, fetchTodo } = todosSlice.actions

"Dynamic Middleware" Middleware

A Redux store's middleware pipeline is fixed at store creation time and can't be changed later. We have seen ecosystem libraries that tried to allow dynamically adding and removing middleware, potentially useful for things like code splitting.

This is a relatively niche use case, but we've built our own version of a "dynamic middleware" middleware. Add it to the Redux store at setup time, and it lets you add and remove middleware later at runtime. It also comes with a React hook integration that will automatically add a middleware to the store and return the updated dispatch method.

import { createDynamicMiddleware, configureStore } from '@reduxjs/toolkit'

const dynamicMiddleware = createDynamicMiddleware()

const store = configureStore({
  reducer: {
    todos: todosReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().prepend(dynamicMiddleware.middleware),
})

// later
dynamicMiddleware.addMiddleware(someOtherMiddleware)

Store Adds autoBatchEnhancer By Default

In v1.9.0, we added a new autoBatchEnhancer that delays notifying subscribers briefly when multiple "low-priority" actions are dispatched in a row. This improves perf, as UI updates are typically the most expensive part of the update process. RTK Query marks most of its own internal actions as "low-pri" by default, but you have to have the autoBatchEnhancer added to the store to benefit from that.

We've updated configureStore to add the autoBatchEnhancer to the store setup by default, so that users can benefit from the improved perf without needing to manually tweak the store config themselves.

configureStore now also accepts a callback for the enhancers option that receives a getDefaultEnhancers() param, equivalent to how the middleware callback receives getDefaultMiddleware():

const store = configureStore({
    reducer: rootReducer,
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware().concat(loggerMiddleware),
    preloadedState,
    enhancers: (getDefaultEnhancers) =>
      getDefaultEnhancers().concat(anotherEnhancer),
  })

Deprecation Removals

When we first released the early alphas of RTK, one of the main selling points was that you could reuse RTK's action creators as "computed key" fields in the object argument to createReducer, like:

const todoAdded = createAction("todos/todoAdded");

const reducer = createReducer([], {
  [todoAdded]: (state, action) => {}
})

This was possible because createAction overrides the fn.toString() field on these action creators to return the action type string. When JS sees the function, it implicitly calls todoAdded.toString(), which returns "todos/todoAdded", and that string is used as the key.

While this capability was useful early on, it's not useful today. Most users never call createAction, because createSlice automatically generates action creators. Additionally, it has no TS type safety. TS only sees that the key is a string, and has no idea what the correct TS type for action is. We later created the "builder callback" syntax for both createReducer and createSlice.extraReducers, started teaching that as the default, and removed the "object" argument to both of those in an earlier RTK 2.0 alpha.

Because of this, we've now removed the fn.toString() override. If you need to access the type string from an action creator function, those still have a .type field attached:

const todoAdded = createAction("todos/todoAdded");
console.log(todoAdded.type) // "todos/todoAdded"

We've also removed the standalone export of getDefaultMiddleware, which has been deprecated ever since we added the callback for the `configureStore.middlewa...

Read more

v2.0.0-alpha.5

18 Apr 16:30
Compare
Choose a tag to compare
v2.0.0-alpha.5 Pre-release
Pre-release

This is an alpha release for Redux Toolkit 2.0. This release adds a new combineSlices API for reducer injection, has many changes to our build setup and published package contents, updates the redux dep to the latest alpha, updates the immer dep to 10.0 final, includes the latest changes from 1.9.x, and has breaking changes.

Changelog

New combineSlices API

The Redux core has always included combineReducers, which takes an object full of "slice reducer" functions and generates a reducer that calls those slice reducers. RTK's createSlice generates slice reducers + associated action creators, and we've taught the pattern of exporting individual action creators as named exports and the slice reducer as a default export. Meanwhile, we've never had official support for lazy-loading reducers, although we've had sample code for some "reducer injection" patterns in our docs.

This release includes a new combineSlices API that is designed to enable lazy-loading of reducers at runtime. It accepts individual slices or an object full of slices as arguments, and automatically calls combineReducers using the sliceObject.name field as the key for each state field. The generated reducer function has an additional .inject() method attached that can be used to dynamically inject additional slices at runtime. It also includes a .withLazyLoadedSlices() method that can be used to generate TS types for reducers that will be added later. See #2776 for the original discussion around this idea.

For now, we are not building this into configureStore, so you'll need to call const rootReducer = combineSlices(.....) yourself and pass that to configureStore({reducer: rootReducer}).

We don't have documentation added for these features yet, but here's example usages from the combineSlices PR tests:

Basic usage: a mixture of slices and standalone reducers passed to combineSlices
const stringSlice = createSlice({
  name: 'string',
  initialState: '',
  reducers: {},
})

const numberSlice = createSlice({
  name: 'number',
  initialState: 0,
  reducers: {},
})

const booleanReducer = createReducer(false, () => {})

const api = createApi(/*  */)

const combinedReducer = combineSlices(
  stringSlice,
  {
    num: numberSlice.reducer,
    boolean: booleanReducer,
  },
  api
)
expect(combinedReducer(undefined, dummyAction())).toEqual({
  string: stringSlice.getInitialState(),
  num: numberSlice.getInitialState(),
  boolean: booleanReducer.getInitialState(),
  api: api.reducer.getInitialState(),
})
Basic slice reducer injection
// Create a reducer with a TS type that knows `numberSlice` will be injected
const combinedReducer =
  combineSlices(stringSlice).withLazyLoadedSlices<
    WithSlice<typeof numberSlice>
  >()

// `state.number` doesn't exist initially
expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined)

// Create a new reducer with `numberSlice` injected
const injectedReducer = combinedReducer.inject(numberSlice)

// `state.number` now exists
expect(injectedReducer(undefined, dummyAction()).number).toBe(
  numberSlice.getInitialState()
)

Selectors support in createSlice

The existing createSlice API now has support for defining selectors directly as part of the slice. By default, these will be generated with the assumption that the slice is mounted in the root state using slice.name as the field, such as name: "todos" -> rootState.todos. You can call sliceObject.getSelectors(selectSliceState) to generate the selectors with an alternate location, similar to how entityAdapter.getSelectors() works.

Slice selectors
const slice = createSlice({
  name: 'counter',
  initialState: 42,
  reducers: {},
  selectors: {
    selectSlice: (state) => state,
    selectMultiple: (state, multiplier: number) => state * multiplier,
  },
})

// Basic usage
const testState = {
  [slice.name]: slice.getInitialState(),
}
const { selectSlice, selectMultiple } = slice.selectors
expect(selectSlice(testState)).toBe(slice.getInitialState())
expect(selectMultiple(testState, 2)).toBe(slice.getInitialState() * 2)

// Usage with the slice reducer mounted under a different key
const customState = {
  number: slice.getInitialState(),
}
const { selectSlice, selectMultiple } = slice.getSelectors(
  (state: typeof customState) => state.number
)
expect(selectSlice(customState)).toBe(slice.getInitialState())
expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2)

Build Setup Updates

We've switched our build setup to use tsup, an ESBuild-powered build framework. This release should have identical build artifacts to 2.0.0-alpha.4, but let us know if there are any issues!

Immer 10.0

Immer 10.0 is now final, and has several major improvements and updates:

  • Much faster update perf
  • Much smaller bundle size
  • Better ESM/CJS package formatting
  • No default export
  • No ES5 fallback

We've updated RTK to depend on the final Immer 10.0 release .

Redux 5.0 alpha and TS types updates

We've updated RTK to use the latest Redux 5.0-alpha.5 release, which tweaks the Reducer type, drops the internal $CombinedState type, and updates middleware types to default to unknown for actions.

For RTK, we've improved type inference for store enhancers, especially those that add additional fields to the state or store.

What's Changed

Full Changelog: v2.0.0-alpha.4...v2.0.0-alpha.5

v1.9.5

18 Apr 02:53
Compare
Choose a tag to compare

This bugfix release includes notable improvements to TS type inference when using the enhancers option in configureStore, and updates the listener middleware to only check predicates if the dispatched value is truly an action object.

What's Changed

  • update to latest remark-typescript-tools by @EskiMojo14 in #3311
  • add isAction helper function, and ensure listener middleware only runs for actions by @EskiMojo14 in #3372
  • Allow inference of enhancer state extensions, and fix inference when using callback form by @EskiMojo14 in #3207

Full Changelog: v1.9.4...v1.9.5

v1.9.4

17 Apr 01:46
Compare
Choose a tag to compare

This bugfix release includes tweaks to RTKQ options handling, tweaks for perf updates, dependency updates, and updates to our CI tooling.

Also, please check out our ongoing RTK 2.0 alpha releases! They have significant improvements to bundle size, ESM/CJS compatibility, TS typings, and reducer update performance. We're looking for real-world feedback on behavior, performance, and any issues you might run into.

Changelog

RTK Query Options Updates

Passing transformResponse as part of enhanceEndpoints can now override the TS type of the original data.

fetchBaseQuery now properly checks for a global responseHandler option.

Performance and Internals

RTK Query now uses Immer's original() to do comparisons inside of copyWithStructuralSharing, which should significantly speed up performance when applying changes from re-fetched data.

RTKQ's internal subscriptionUpdated action is now marked as batchable.

We've updated dependencies to Immer 9.0.21, Reselect 4.1.8, and Redux 4.2.1.

CI Updates

We've added a suite of example apps built with different frameworks such as CRA 4, CRA 5, Next, and Vite, as well as examples that check for compatibility in Node with CJS and ESM modes and with various TS module resolution modes.

What's Changed

Full Changelog: v1.9.3...v1.9.4

v2.0.0-alpha.4

03 Apr 17:53
Compare
Choose a tag to compare
v2.0.0-alpha.4 Pre-release
Pre-release

This is an alpha release for Redux Toolkit 2.0. This release has many changes to our build setup and published package contents, updates the redux and redux-thunk deps to the latest alphas, updates the immer dep to the latest 10.x beta, and has breaking changes.

npm i @reduxjs/toolkit@alpha

yarn add @reduxjs/toolkit@alpha

Also see the [email protected] release notes.

Changelog

ESM/CJS Package Compatibility

The biggest theme of the Redux v5 and RTK 2.0 releases is trying to get "true" ESM package publishing compatibility in place, while still supporting CJS in the published package.

Earlier alphas made changes to the package.json contents and published build artifacts in an attempt to get ESM+CJS compat working correctly, but those alphas had several varying compat issues.

We've set up a battery of example applications in the RTK repo that use a variety of build tools (currently CRA4, CRA5, Next 13, and Vite, Node CJS mode, and Node ESM mode), to verify that Redux and Redux Toolkit compile, import, and run correctly with both TS and various bundlers. We've also set up a check using a custom CLI wrapper around https://arethetypeswrong.github.io to check for potential packaging incompatibilities.

This release changes the names and contents of the published build artifacts, and the various exports/module/main fields in package.json to point to those.

We already tried to point to ESM build artifacts as the default. That should hopefully be be more consistent now.

As of this release, we think we have ESM+CJS compat working correctly, but we ask that the community try out the alphas in your apps and let us know of any compat problems!

Note: The one known potential issue is that TypeScript's new moduleResolution: "node16" mode may see a mismatch between the ESM artifacts and the TS typedefs when imported in a Node CJS environment, and [that may allow hypothetically-incorrect import usage. (See ongoing discussion in https://github.com/arethetypeswrong/arethetypeswrong.github.io/issues/21 .) In practice, we think that probably won't be a concern, and we'll do further investigation before a final release.

Dropping UMD Builds

Redux has always shipped with UMD build artifacts. These are primarily meant for direct import as script tags, such as in a CodePen or a no-bundler build environment.

For now, we're dropping those build artifacts from the published package, on the grounds that the use cases seem pretty rare today.

We do have browser-ready ESM build artifacts included, which already have process.env.NODE_ENV compiled away for either development or production behavior. These can be loaded via a script tag that points to that file on Unpkg.

If you have strong use cases for us continuing to include UMD build artifacts, please let us know!

Immer 10 Beta

Immer 10 is now in beta. It has several major changes, including faster perf, dropping ES5 environment support, and switching from a default export to only named exports.

We've updated RTK to depend on [email protected].

Performance testing in an artificial RTKQ stress test project showed significant perf increases when dealing with many RTKQ-connected components loading, and that was directly due to Immer-powered reducers executing much faster.

While reducers are not usually the bottleneck in React+Redux apps, this looks like it will be a very nice improvement!

What's Changed

Full Changelog: v2.0.0-alpha.2...v2.0.0-alpha.4

v1.9.3

21 Feb 04:12
Compare
Choose a tag to compare

This release fixes a couple issues with the skip/skipToken options for query hooks, and makes a small perf tweak to serializing query args.

Changelog

Skip Behavior

We made a change in v1.9.0 that tried to make some skip behavior more consistent, including clearing out the cached data. However, we had overlooked that our own docs actually said "skipping a query will keep the cached data", and several users pointed this out as they'd been relying on that behavior.

We've reverted that change. Now, setting {skip: true} or skipToken for a query with existing results will keep the data value (reflecting the last successful query), but currentData will be undefined (reflecting the current settings).

We also identified and fixed an issue that could cause subscription entries to leak under a specific combination of timing and settings changes.

Query Arg Serialization Perf

RTKQ relies on serializing query arguments to serve as the cache keys, with the default using JSON.stringify() + some logic for sorting keys. There was a report that in some apps, large query arg objects could take a while to stringify and this was being done repeatedly. We've added a WeakMap-based cache for query args to avoid re-serializing existing arg values.

What's Changed

Full Changelog: v1.9.2...v1.9.3

v2.0.0-alpha.2

15 Feb 02:12
Compare
Choose a tag to compare
v2.0.0-alpha.2 Pre-release
Pre-release

This alpha release updates the Redux core package dependency to 5.0.0-alpha.2. This release may have breaking changes.

Changelog

Redux v5 Dependency Update

We've updated the redux dependency from 4.2.x to [email protected]. The Redux v5 branch contains the TS conversion work we did in 2019, a migration to full ESM package compatibility, and some additional types changes and internal tweaks.

In practice, these hopefully shouldn't affect most codebases, but that's why these are major versions and we're publishing these alphas :)

We'd like users to try out these alphas releases in your apps and let us know of any build/typing/runtime errors or compatibility problems!

Please see the Redux v5 alpha release notes for further details:

What's Changed

Full Changelog: v2.0.0-alpha.1...v2.0.0-alpha.2

v1.9.2

28 Jan 21:21
Compare
Choose a tag to compare

This bugfix release fixes a memory leak in createListenerMiddleware, optimizes performance inside serializableMiddleware, adds new options for fetchBaseQuery, adds support for path RegExp exclusions in serializableMiddleware and immutabilityMiddleware, and improves some TS types.

Changelog

Bug Fixes

createListenerMiddleware had a memory leak that turned out to be due to use of Promise.race(). We've restructured the logic to fix that.

fetchBaseQuery now correctly combines global options with endpoint / default options in all cases.

New Options

fetchBaseQuery now supports a jsonReplacer option that will be used when processing JSON.

Both dev check middleware now support regular expressions in the ignoredPaths array in addition to strings. This adds extra flexibility in skipping certain fields.

TS Changes

The CaseReducer type was sometimes incorrectly inferring its return type in rare cases. That's been fixed.

The isAnyOf/isAllOf matcher function TS types have been tweaked to not require an individual first parameter. This allows spreading arrays of matchers as arguments, like const isLoading = isAnyOf(...interestingPendingThunksArray).

Other Changes

The serializableMiddleware now uses a WeakSet if available to cache values it's seen. This should significantly speed up checks against large state values in development builds.

What's Changed

  • fix CaseReducer to infer from argument, not return value by @phryneas in #3054
  • fetchBaseQuery | Add jsonReplacer param by @tophep in #2904
  • Support RegExp in ignoredPaths/ignoredActionPaths by @markerikson in #3129
  • fix(types): export ThunkWithReturnValue interface by @giomogna in #3108
  • remove unnecessary fetchBaseQuery defaults by @phryneas in #3062
  • make isAnyOf friendly for mapped matchers, but making argument optional by @megagon in #3123
  • raceWithSignal method instead of Promise.race by @phryneas in #3021
  • Fix lint problems and enable linting on CI by @thorn0 in #2992
  • Add caching to serializableStateInvariantMiddleware by @GeorchW in #3115
  • Allow TS isolatedModules flag to be set for safer transpilation by @matmannion in #2911

Full Changelog: v1.9.1...v1.9.2

v2.0.0-alpha.1

21 Jan 03:09
Compare
Choose a tag to compare
v2.0.0-alpha.1 Pre-release
Pre-release

This is an alpha release for Redux Toolkit 2.0. This release has breaking changes.

Changelog

ESM Migration

As a literally-major part of the Redux Toolkit 2.0 alpha development work, we've migrated the package definition to be a full {type: "module"} ESM package (with CJS still included for compatibility purposes).

We've done local testing of the published package in several different build tools and environments:

  • Vite 4
  • Create React App 4 and 5
  • Next 13
  • Parcel 2
  • Node 18 (importing as CJS and ESM)

We have not tested it in React Native projects yet, or other non-React projects.

So far, the package structure seems to work okay in each of those environments, but we ask the community to try out this alpha in your own projects and report any breakages you find!

While it's not strictly related to the ESM migration, one potential compat issue is that we no longer transpile any of our build artifacts (other than removing TS syntax), and we do use the optional chaining operator ( ?. ). It's likely that Webpack 4 will find the correct ESM build artifact, but not be able to parse that syntax. You'll need to either upgrade to Webpack 5, or modify your Webpack config to transpile the RTK build artifacts that are imported from node_modules.

Redux-Thunk 3.0 Alpha

Along with the RTK package, we've migrated redux-thunk to ESM as well and published that as 3.0.0-alpha.1. That release also drops the existing default export, and instead has named exports: import { thunk, withExtraArgument } from 'redux-thunk'.

This should not meaningfully affect RTK users, as configureStore already sets up the thunk middleware for you.

Vitest Migration

We've migrated our own test suite from Jest to Vitest, fixing a couple of compat issues.

Related Future Work

Michel Weststrate, author of Immer, has published a roadmap for Immer 10.0. This includes modernization by dropping non-Proxy ES5 compat, dropping the default export, performance improvements, and more. We plan to include Immer 10 in RTK 2.0.

We're restarting discussion around possible additional changes to Reselect in a v5 major version, and would appreciate feedback on possible improvements there.

See the RTK 2.0 planning thread for other work we're considering for 2.0.

What's Changed

Full Changelog: v2.0.0-alpha.0...v2.0.0-alpha.1

v2.0.0-alpha.0

14 Jan 23:31
Compare
Choose a tag to compare
v2.0.0-alpha.0 Pre-release
Pre-release

This is the initial alpha release for Redux Toolkit 2.0. This release has breaking changes.

Please try this out and let us know of any build or runtime incompatibilities beyond the expected breaking changes!

See the RTK 2.0 planning issue for discussion of likely upcoming changes.

Changelog

Removal of Object Object Argument for createReducer and createSlice.extraReducers

As described in the RTK v1.9.0 release notes, we've removed the object form for createReducer and createSlice.extraReducers. Use the "builder callback" form instead.

See the 1.9 release notes for examples of what the necessary changes look like, and details on the available codemod for automatically making this change to your source.

Modernized Build Output Formats

This release drops backwards-compatibility aspects for IE11 in our published package build artifacts:

All module formats (CJS and ESM) now contain code that targets the latest ES spec and no longer backwards-compiled to ES5 syntax

Later 2.0-alpha releases will add full Node ESM support with exports in package.json, and may contain other build artifact changes as well.

Build Tooling Updates

We've updated our own build process to use the latest ESBuild and TS versions.