Skip to content

Releases: reduxjs/redux-toolkit

v1.6.0-alpha.1

24 Apr 22:53

Choose a tag to compare

v1.6.0-alpha.1 Pre-release
Pre-release

This pre-1.6 alpha release integrates the RTK Query source code into the Redux Toolkit repo, publishes the RTK Query APIs as nested endpoints in the @reduxjs/toolkit package, and publishes a docs preview containing the RTK Query API and usage guide docs integrated into the RTK docs site. We've also bumped our Redux dependency to 4.1.0.

This release also contains the changes from 1.6.0-alpha.0, including Immer 9.x and the improvements to createAsyncThunk and createEntityAdapter.

Note: we have published additional bugfixes since alpha.1. Please see the releases list for details and be sure to update to @reduxjs/toolkit@next.

Installation:

npm i @reduxjs/toolkit@next

yarn add @reduxjs/toolkit@next

Changelog

RTK Query Integration

RTK Query is a data fetching and caching library built for Redux. It's most similar to React Query, Apollo, Urql, and SWR. The idea is that you just say "here's my URL endpoint and some query params", and it handles the entire process of:

  • Starting to fetch the data when needed
  • Managing loading state
  • Caching the data
  • Re-rendering your component when the loading state changes or the data is available
  • Clearing the cache when the last component that needs the data is unmounted
  • Enabling automatic polling of the data if desired

So, instead of having to write a bunch of thunks, action creators, reducers, useEffects, and dispatching yourself, you just do:

// src/services/pokemon.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
  reducerPath: 'pokemonApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
  endpoints: (builder) => ({
	getPokemonByName: builder.query({
	  query: (name: string) => `pokemon/${name}`,
	}),
  }),
})

// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetPokemonByNameQuery } = pokemonApi


// src/App.tsx
import { useGetPokemonByNameQuery } from './services/pokemon'

export default function App() {
  // Using a query hook automatically fetches data and returns query values
  const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
  
  // render logic here
}

We originally developed RTK Query as a standalone package at https://github.com/rtk-incubator/rtk-query in order to enable faster iteration during the alpha process.

Now that the RTK Query APIs have stabilized, we've merged all of the RTK Query source code and docs content back into the main RTK repo. From there, we've updated our build tooling and package publishing to include the RTK Query code inside the @reduxjs/toolkit package, but as separate nested entry points.

// Existing RTK APIs
import { createSlice } from '@reduxjs/toolkit'
// RTK Query APIs, with UI-agnostic logic
import { createApi } from '@reduxjs/toolkit/query'
// Same RTK Query APIs, but with React-specific functionality built in
import { createApi } from '@reduxjs/toolkit/query/react'

If you've been using RTK Query in its standalone alpha package, you should be able to migrate by installing this RTK alpha, and just switching your imports to match the examples above.

Since these are separate entry points from the root package, you won't pay any bundle size cost unless you specifically import the RTK Query APIs.

We have not yet done extensive testing of the merged package - that's why this is an alpha! That said, we've successfully been able to locally publish a preview version of the package and use it in a standalone version of the RTKQ "kitchen sink with React" demo app, which is a standard CRA project. We've also verified that the app builds correctly with both TS 4.1 (including named hooks exports using string literal syntax) and TS 4.0 (with just the per-endpoint hook subfields).

For visualization purposes, here's what the bundle size analysis for that app looks like:

image

In general, we believe that this alpha should run correctly in varying environments, but we specifically request that users try this out and let us know if you run into any problems.

We also expect some additional final tweaks to the RTKQ APIs before 1.6 is released, but do not expect any large breaking changes.

RTK Query Docs

We've also merged the RTK Query docs content and added it to a preview version of the RTK docs site. We'll leave this preview up during the RTK 1.6 pre-release process. Here's the links to the primary new docs pages:

Note that we still need to fill out additional docs content for the RTK APIs and update some of the examples! We'll be working to finish that before the final release.

Redux 4.1.0

Since we just released Redux 4.1.0, this release also bumps our Redux dependency to match that. This will shave about 1K off your existing min+gz bundle sizes.

Changes

https://github.com/reduxjs/redux-toolkit/compare/v1.6.0-alpha.0..v1.6.0-alpha.1

v1.6.0-alpha.0

04 Apr 02:13

Choose a tag to compare

v1.6.0-alpha.0 Pre-release
Pre-release

This initial pre-1.6 alpha release reworks our build tooling to enable upcoming changes, updates dependencies, adds new entity adapter CRUD methods, and tweaks async thunk options.

We have several major improvements planned for later pre-1.6 alphas, including merging the "RTK Query" APIs back into RTK itself and making those available as subpackages.

Changelog

Build Tooling Updates

We've replaced our existing TSDX build setup with a new custom setup based on ESBuild + TypeScript (thanks to @hardfist for doing all the work on that!). In theory, we should have all of the same build artifacts we had before, targeting the same browser and ES spec levels. We did add a new set of "modern" build artifacts that target ES2017, with builds for bundlers and browwsers. However, we can't officially list those in package.json yet, as apparently defining an exports key can break Node and is considered a breaking change. We'll look at flipping that on in a future 2.0 release.

Please let us know if you see any bugs that may be related to the build tooling and bundling changes!

Dependency Updates

We've updated Immer to 9.0.x. Also, we've updated Redux to the hot-off-the-presses 4.1.0-alpha.0, which shrinks bundle size by extracted error messages in production. Again, please report any problems you run into.

New Entity Adapter Replace Methods

createEntityAdapter already had methods to add, upsert, and update items. We've added setOne and setMany, which let you add or entirely replace existing items.

Async Thunk Improvements

createAsyncThunk promises now have a .unwrap() method that returns a promise for the actual result payload, or throws an error if the promise was rejected. This simplifies the use case of working with the thunk result in components.

createAsyncThunk also now accepts an idGenerator option to let you swap out the default nanoid() ID generation utility for your own, such as uuid4.

Other Updates

We've done a bit of internal cleanup in a few functions to simplify and shorten implementations.

configureStore now checks for accidentally returning undefined for a middleware array, or undefined entries in the array.

The PreloadedState type should now work better with TS 4.3+.

Changes

v1.5.1...v1.6.0-alpha.0

v1.5.1

26 Mar 03:25

Choose a tag to compare

This release updates createReducer/createSlice to ensure initial state gets frozen, optimizes the performance of the immutability and serializability dev check middleware, and re-exports the original and isDraft APIs from Immer.

Changes

Freezing Initial State

Immer has always frozen its result states in development, and as of Immer 8, does so in production as well. Since both createReducer and createSlice use Immer, all their result states get frozen.

However, the initial state was never being run through Immer, and thus was never being frozen. That meant it was actually possible to mutate initial state values.

We now run the initial state through an Immer no-op reducer, just to ensure that it's properly frozen at all times.

Dev Check Middleware Optimizations

The immutability and serializability dev check middleware act as safety rails to catch common mistakes. However, both of them do so by running deep recursive checks on all of your Redux state, after every dispatched action. Those deep checks are often very expensive, especially as your state grows larger, and the performance can add noticeable lag in dev. RTK offers options for turning them off or ignoring specific slices of state, and warns if they're taking too long, but those are escape hatches we would rather people not use because they lose the safety benefits.

In this release, we've optimized both middleware to run significantly faster.

The immutability middleware now considers frozen objects to be immutable by default, and will stop recursing when it sees a frozen value. This means that in most cases, the runtime of the immutability middleware is effectively 0ms, as it only has to do a few quick checks on your root state slice values without going any deeper. We've also optimized the nested keypath calculation logic, which cuts down on runtime significantly if it does actually have to recurse.

We've also applied the keypath optimizations to the serializability middleware as well. While this middleware can't benefit from the frozen checks, the keypath optimizations have cut its runtime down by about 70% in benchmarks.

Additional Immer Re-Exports

We now export the original and isDraft APIs from Immer for completeness.

This release also updates the Immer dependency to 8.0.1+ to address a vulnerability in Immer. Our dependency was already 8.0.0+, so strictly speaking no update was necessary - users just needed to update their own local dependencies to get the newer Immer version.

Docs Updates

We've made several updates to the RTK docs:

  • The existing tutorials in the RTK docs site have been removed, and we now point to the "Redux Essentials" and the "Redux Fundamentals" tutorials in the Redux core docs so that there's only one place to have to read through tutorials
  • We've added a couple "quick start" tutorial pages that show the bare basics of setting up RTK and using it with TypeScript

Changelog

v1.5.0...v1.5.1

v1.5.0

28 Nov 19:39

Choose a tag to compare

This release updates Immer to v8.x, adds a set of "matcher" utilities to simplify checking if dispatched actions match against a range of known action types, adds additional customization of async thunk error handling, and adds miscellaneous improvements around TS types and some reducer edge cases.

Changes

Immer v8

In RTK v1.4, we upgraded Immer to v7.x, which added a new current API for debugging.

Immer recently released v8.0.0, which changes its default behavior around auto-freezing state values. Previously, Immer only auto-froze data in development mode, partly under the assumption that freezing would be slower overall. Due to internal changes in Immer 7, Immer now checks if data is frozen and can bail out of some of its processing early. As a result, Immer 8 switches to always freezing return values, even in production, Per discussion in the Immer issues linked from the v8 release announcement, this apparently is actually faster on average.

This is a noticeable change in behavior, but we consider it not breaking for RTK on the grounds that you shouldn't be mutating state outside of a reducer anyway, so there shouldn't be any visible effect on correctly-written Redux logic.

We've updated our Immer dependency to v8.x.

Per the Immer docs on auto-freezing, it may be more efficient to shallowly pre-freeze very large data that won't change by using Immer's freeze utility. RTK now re-exports freeze as well.

Action Matching Utilities

In RTK v1.4, we added the ability to write "matching reducers" that can respond to more than one potential action based on a predicate function, such as builder.addMatcher( (action) => action.type.endsWith('/pending'), reducer).

Many users have asked for the ability to directly list a series of RTK-generated action creators as possible actions to match against. For RTK 1.5, we're adding several utilities that will help handle that process.

First, we've added isAnyOf(matchers) and isAllOf(matchers). These effectively perform boolean || and && checks, and accept an array containing RTK action creators or action-matching predicates. They return a new matching callback that can be passed to builder.addMatcher().

const isActionSpecial = (action: any): action is SpecialAction => {
  return action.payload === 'SPECIAL'
}

const thunkA = createAsyncThunk<string>('a', () => 'result');

// later
createSlice({
  name,
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addMatcher(isAllOf(isActionSpecial, thunkA.fulfilled), reducer);
  }
})

When used with TypeScript, isAllOf and isAnyOf will correctly narrow down the possible type of action based on the actions they match against.

We've also added a set of matching utilities specifically meant to help check if a given action corresponds to the lifecycle actions dispatched by some specific async thunks. isPending, isFulfilled, isRejected, isRejectedWithValue, and isAsyncThunkAction all accept an array of thunks generated by createAsyncThunk, and will match the corresponding action types from those thunks:

const thunkA = createAsyncThunk<string>('a', () => 'result')
const thunkB = createAsyncThunk<string>('b', () => 'result')
    
// later
createSlice({
  name,
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addMatcher(isFulfilled(thunkA, thunkC), reducer);
  }
})

They can also be used as standard TS type guards:

if (isFulfilled(action)) {
  // TS will narrow the type of `action` to match the standard "fulfilled" fields
}

createAsyncThunk Improvements

We've fixed a bug in the unwrapResult utility where it didn't correctly handle use of rejectWithValue() in the thunk. It will now correctly re-throw the value passed into rejectWithValue().

The auto-generated requestId is now attached to the promise returned when the thunk is dispatched, as is the arg that was passed into the thunk when it was dispatched.

We've added a new serializeError callback option to createAsyncThunk. By default, createAsyncThunk will convert thrown errors into a serializable equivalent format, but you can override the serialization and provide your own callback if desired.

Draft-Safe Selectors

Memoized selectors created with Reselect's createSelector don't work well with Immer's Proxy-wrapped draft states, because the selectors typically think the Proxy object is the same reference and don't correctly recalculate results as needed.

We've added a createDraftSafeSelector API that lightly wraps createSelector by checking if the initial argument (usually state) is actually an Immer draft value, and if so, calling current(state) to produce a new JS object. This forces the selector to recalculate the results.

We've also updated createEntityAdapter's getSelectors API to use these draft-safe selectors.

In general, using selectors inside of reducers is an unnecessary abstraction - it's fine to access data like state.entities[id].value = 123. However, a number of users have expressed an interest in doing so, so we've made these changes to help accommodate that.

Other Changes

We now export our internal isPlainObject implementation.

If an Immer-powered reducer has null as its value, returning undefined is now accepted.

TS types for case reducers have been tweaked to allow returning Draft<T> to handle an edge case with generics.

The TS types for the devTools.serialize option in configureStore have been updated to correctly match the actual values.

The RTK docs now use a custom Remark plugin created by @phryneas, which allows us to write real TS code for code blocks, compile it to verify it type-checks correctly, and generate a plain JS version of that exact same example, with the TS and JS variations viewable in tabs for each code block.

You can see that in action in pages like the createSlice API docs:

https://redux-toolkit.js.org/api/createSlice

Changelog

v1.4.0

22 Jun 00:57

Choose a tag to compare

This release updates Immer to v7.x, adds new options for defining "matcher" and "default case" reducers, and adds a new option for adding middleware to the store.

Changes

Immer v7

Immer recently released v7.0.0. The main feature is a new current API, which takes a Proxy-wrapped draft value and returns a finalized snapshot of the draft at that point in time.

Logging draft states in RTK reducers has always been difficult, as browsers display proxies in a hard-to-read format. This utility allows a return to logging partially-updated data to see the results, like console.log(current(state)).

We've updated our Immer dependency to v7.x, and now export current as part of our public API.

"Matcher" and "Default Case" Reducers

createReducer has always been designed to handle exact action types. Both the object form and "builder callback" form let you specify a specific action type string to match against.

This is by far the most common use case, but we didn't previously have a way to handle matching a range of possible action types based on some criteria. We also had some requests to add some kind of a "default case" handler, similar to the default keyword in a switch statement.

The builder callback form of createReducer now supports two new methods in addition to the existing builder.addCase method:

  • builder.addMatcher accepts a predicate function that looks like (action: Action) => boolean, and a case reducer. If the matcher returns true, the case reducer will run. Multiple matchers may be added to createReducer, and all matchers that return true will run in the order they were defined after any exact case match has run.
  • builder.addDefaultCase will run a reducer if no other cases or matchers have run.

Example:

const increment = createAction('increment')
const decrement = createAction('decrement')
createReducer(0, builder =>
  builder
    .addCase(increment, (state, action) => {
      // action is inferred correctly here
    })
    // You can chain calls, or have separate `builder.addCase()` lines each time
    .addCase(decrement, (state, action) => {})
    // You can match a range of action types
    .addMatcher(
      action => action.endsWith('rejected'),
      (state, action) => {}
    )
    // and provide a default case if no other handlers matched
    .addDefaultCase((state, action) => {})
)

The new builder methods work the same way in the extraReducers field of createSlice as well.

Middleware Creation

We already export getDefaultMiddleware to allow you to customize the middleware setup when creating the store. However, running [...getDefaultMiddleware, otherMiddleware] often loses type information when used with TypeScript, and middleware types may need to reference the root state type as well.

The middleware option for configureStore can now be a callback function that receives a strongly-typed version of getDefaultMiddleware as an argument, and should return the final middleware array. It also includes strongly-typed concat and prepend methods that preserve type information better than spreading arrays:

const store = configureStore({
  reducer: rootReducer,
  middleware: getDefaultMiddleware => getDefaultMiddleware().concat(logger)
})

Bug Fixes

  • createAsyncThunk could sometimes skip handling a promise under certain conditions, causing an "Uncaught Promise" warning.
  • The updateMany CRUD method of createEntityAdapter wasn't properly merging together multiple changes for the same item ID

Typing Updates

The condition argument for createAsyncThunk is now defined as returning boolean | undefined. The actual code explicitly checks if condition() !== false, so returning undefined is legal, but the typing was too strict.

We now export the return type for createAsyncThunk for reuse.

Docs Updates

We've extensively updated the createReducer and createSlice pages to cover the new builder methods, and configureStore and getDefaultMiddleware to cover the new middleware syntax.

We've extracted the info on the immutability and serializability dev check middleware into their own separate API reference pages.

We've added a Usage Guide section on handling "non-serializable value" warnings, including examples of configuration for use with Redux-Persist and React-Redux-Firebase. Related to that, the serializability warning message now includes a link to this section.

The API reference section now has subcategories for "Store Setup", "Reducers and Actions", and "Other".

We've enabled Dark Mode for Docusaurus, including tweaks to colors for better contrast.

Changes

  • Update docs on serializability usage and dev check middleware (@markerikson - #630)
  • add addMatcher to builder notations & actionMatchers argumen… (@phryneas - #610)
  • Add styling for blockquotes on darkmode (@msutkowski - #615)
  • Add section to usage guide on working with non-serializable data (@cloeper - #623)
  • Fixed multiple updates with same id in updateMany (@jakeboone02 - #621)
  • Bump immer to v7, export current util, add usage docs (@mutkowksi - #604)
  • docs: implement dark mode (@sreetamdas - #575)
  • Export return type of createAsyncThunk (@smrq - #574)
  • Prevent unhandled promises in createAsyncThunk (@msutkowski - #570)
  • Adding correctly typed prepend` and concat` to the array… (@phryneas - #559)
  • add middlewareBuilder notation with type-curried arguments to co… (@phryneas - #549)
  • Allow undefined return type for createAsyncThunk options (@melanieseltzer - #595)

v1.3.6...v1.4.0

v1.3.6

09 May 18:41

Choose a tag to compare

This release fixes a couple edge cases with Immer usage and reducers, and exposes the typePrefix field from thunks generated by createAsyncThunk.

Changes

Immer Reducer Fixes

The createEntityAdapter CRUD methods can be used as either standalone reducers (in which case they call createNextState() internally) or "mutating" helper functions if given an existing Immer Draft value. However, createReducer always assumed you were using the reducer standalone.

If you were trying to wrap createReducer and pass in a Draft value, changes inside wouldn't be reflected in the external Draft. We've updated createReducer to check if the incoming state value is actually a `Draft.

Also, the removeAll CRUD method from createEntityAdapter wasn't working correctly when used as a mutating helper, for similar reasons. We've tweaked the logic there to work right.

Thunk Type Prefix

createAsyncThunk accepts a typePrefix string as its first argument, and uses that to generate the pending/fulfilled/rejected action types it dispatches. We had some requests to expose that type string for later usage, so the thunk function now has a thunk.typePrefix field containing that string.

Changelog

v1.3.5...v1.3.6

v1.3.5

19 Apr 20:04

Choose a tag to compare

This release adds the ability to cancel async thunks before execution, improves TS thunk types, and fixes a broken middleware option name change.

Changes

Async Thunk Cancellation

The createAsyncThunk API already had support for signaling cancellation of in-progress requests via an AbortController. However, there was no way to skip executing the payload creator callback itself, or skip dispatching the pending action.

We've added a condition option to createAsyncThunk that allows you to run checks before the payload creator is executed, and bail out of the thunk entirely if desired by returning false:

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  },
  {
    condition: (userId, { getState, extra }) => {
      const { users } = getState()
      const fetchStatus = users.requests[userId]
      if (fetchStatus === 'fulfilled' || fetchStatus === 'loading') {
        // Already fetched or in progress, don't need to re-fetch
        return false
      }
    }
  }
)

Thunk Typing Improvements

We've updated the createAsyncThunk TS types to fix an issue when there is no thunk argument, and to make it easier to wrap createAsyncThunk in your own abstractions. See #486 / #489 for the issues and #502 / #512 for the updates.

Immutable Middleware Options

When we inlined the immutable check middleware, we ended up changing the ignore option name to ignoredPaths. We've added handling for ignore for backwards compatibility just in case anyone was relying on that. We've also better documented the options for the serializable check middleware. See #491, #492, and #510 .

Changelog

  • allow to skip AsyncThunks using a condition callback (@phryneas - #513)
  • payloadCreator arg argument => asyncThunk arg type (@phryneas - #502)
  • export AsyncThunkPayloadCreator type (@phryneas - #512)
  • Update docs, add test, alias ignore->ignoredPaths (@msutkowski - #492)
  • Add missing parameters to docs for serializable middleware (@msutkowski - #510)

v1.3.4...v1.3.5

v1.3.4

05 Apr 19:13

Choose a tag to compare

This release updates our internal nanoid implementation, and exports it for general usage.

Changes

Export nanoid

The new createAsyncThunk API we added in v1.3.0 auto-generates a unique request ID every time it's called, so that your reducers can distinguish between separate calls if necessary. To do this, we inlined a copy of the nanoid/non-secure API into RTK.

The nanoid library just released a new version, so we've updated our inlined copy to match the implementation of nanoid/non-secure as of 3.0.2.

Since the API is already in the codebase, we've exported it publicly in case it's useful. Usage:

import { nanoid } from '@reduxjs/toolkit'

console.log(nanoid())
// 'dgPXxUz_6fWIQBD8XmiSy'

Changelog

v1.3.3...v1.3.4

v1.3.3

04 Apr 18:56

Choose a tag to compare

This release improves serializability checking in actions, and exports additional types.

Changes

Action Serializability Checks

The serializability check middleware checks the contents of all dispatched actions. When we added createAsyncThunk in 1.3, we tried to exclude the meta.args path from those checks, because users may want to pass non-serializable values to their thunks, and the args are automatically added to the actions without the user explicitly putting them there.

However, the field name was changed from meta.args to meta.arg late in development, and the middleware wasn't updated to match, leading to some false positive warnings. We've fixed that, and added additional middleware options for ignoring paths in actions.

Type Exports

Per request, we've exported ThunkDispatch from Redux Thunk, and the rest of the internal typedefs related to entities.

Changelog

  • Fix/entity types (@markerikson - #477)
  • allow configuration of createSerializableStateInvariantMiddleware ignoredActionPaths, update default value, add tests (@phryneas - #457)
  • Export ThunkDispatch from redux-thunk. (@sammyers - #473)

v1.3.2...v1.3.3

v1.3.2

28 Mar 01:42

Choose a tag to compare

When we inlined the immutability check middleware in 1.3.0, we documented the createImmutableInvariantMiddleware API, but forgot to export it. That's been fixed.

Changelog

  • Export createImmutableStateInvariantMiddleware, fix typo (#449) e3c2cf0

v1.3.1...v1.3.2