Skip to content

Releases: reduxjs/redux-toolkit

v1.3.0-alpha.4

16 Feb 06:43

Choose a tag to compare

v1.3.0-alpha.4 Pre-release
Pre-release

This alpha release rearranges the TS generic types of createAsyncThunk to fix broken usage.

Changes

createAsyncThunk Types

createAsyncThunk gives you access to getState in the thunkAPI object argument. However, we hadn't tried to exercise that yet in our tests, and it turns out there was no valid way to specify the correct type of the state returned by getState.

We've rearranged the generic types and tweaked the defaults. You should now be able to use it without specifying any generics for the most common case (returning a value, with a potential argument for the payload callback), and specify three types if you need to declare what the state type is:

// basic usage:
const thunk1 = createAsyncThunk("a", async (arg: string) => {
    return 42
})
// infers: return = Promise<number>, arg = string

// specify state type for getState usage
const thunk2 = createAsyncThunk<Promise<number>, string, RootState>(
    "a",
    async (arg: string, {getState}) => {
        const state = getState();
        return 42;
    }
)
// declared: return = Promise<number>, arg = string, state: RootState

We have some ideas for additional potential improvements to these types that may make usage simpler, so please keep an eye out for further alpha releases.

Documentation

A first draft of API documentation for createAsyncThunk is now available:

createAsyncThunk draft API reference

Changes

v1.3.0-alpha.3...v1.3.0-alpha.4

See PR #352: Port ngrx/entity and add createAsyncThunk for the complete alpha changes.

v1.3.0-alpha.3

15 Feb 23:23

Choose a tag to compare

v1.3.0-alpha.3 Pre-release
Pre-release

This alpha release alters the error-handling behavior of createAsyncThunk (again! 😁 )

Changes

createAsyncThunk Error Handling

In alpha.2, we tried having the thunk always re-throw caught errors. That didn't work out, because they always show up in the browser's console as unhandled exceptions.

Instead, the thunk now always returns a resolved promise containing the last action dispatched, which will be either the fulfilled action or the rejected action. We also export an unwrapAction utility that will either return action.payload if fulfilled or throw action.error if rejected, allowing the dispatching code to chain off the promise if desired:

import { unwrapResult } from '@reduxjs/toolkit'

// in the component
const onClick = () => {
  dispatch(fetchUserById(userId))
    .then(unwrapResult)
    .then(originalPromiseResult => {})
    .catch(serializedError => {})
}

Aborting Requests

Since createAsyncThunk accepts a promise callback, we have no way of knowing if or how the async logic can be canceled. However, we now provide a way for your logic to signal that cancellation needs to occur. An AbortController instance will be created for each request, and abort method will be attached to the returned promise that calls abortController.abort() and rejects the promise. The corresponding abortController.signal object will be passed in to your payload creator callback in the thunkAPI object as thunkAPI.signal, allowing your async logic to check if a cancellation has been requested.

Meta Field Names

The action.meta field in each action object previously looked like: {args, requestId}

The args field was renamed to arg to indicate it's only one value.

For the pending and fulfilled actions, action.meta is now: {arg, requestId}

The rejected action now includes details on whether the request was aborted, and
action.meta is now: {arg, requestId, aborted, abortReason}

Documentation

A first draft of API documentation for createAsyncThunk is now available:

createAsyncThunk draft API reference

Changes

v1.3.0-alpha.2...v1.3.0-alpha.3

See PR #352: Port ngrx/entity and add createAsyncThunk for the complete alpha changes.

v1.3.0-alpha.2

15 Feb 04:39

Choose a tag to compare

v1.3.0-alpha.2 Pre-release
Pre-release

This alpha release alters the error-handling behavior of createAsyncThunk.

Changes

createAsyncThunk Error Handling

createAsyncThunk catches rejected promises from the promise payload callback, and dispatches a "rejected" action in response with the error value. That means that the thunk itself always returns a resolved promise.

We had a request to re-throw errors from the thunk, in case the calling code wants to chain off the promise or handle it further, so we've implemented that.

We've also removed the "finished" action that was previously being dispatched in a finally {} clause at the end, as it shouldn't truly be necessary - app logic should just need to respond to either the "fulfilled" or "rejected" actions.

When an error is caught in the thunk, we try to put it into the "rejected" action. But, since JS Error objects aren't actually serializable, we now check for Error objects and serialize them into plain JS objects with any of the relevant fields ( {name?, message?, stack?, code?}), or just the value itself if it's not an Error. Unfortunately, since the err value in a catch(err) {} clause doesn't have a type, the action.error field will come through as a type of any either way.

Finally, we've reordered the logic inside to avoid cases where an error while dispatching "success" gets swallowed and triggers the "AJAX failed" handling.

v1.3.0-alpha.1...v1.3.0-alpha.2

v1.3.0-alpha.1

14 Feb 05:17

Choose a tag to compare

v1.3.0-alpha.1 Pre-release
Pre-release

This release makes several noticeable changes to the alpha createEntityAdapter and createAsyncThunk APIs that were introduced in v1.3.0-alpha.0.

Changes

createEntityAdapter

We made several changes to the type definitions for EntityAdapter and its related types:

  • Replaced separate overloads for handling string and number IDs with a single type EntityId = string | number type, and used that everywhere
  • Added EntityAdapter method type overloads for correct inference of PayloadAction<T> when passed directly as a case reducer inside of createSlice's reducers field
  • Removed the removeMany(Predicate) overload, as we discourage passing functions inside of actions

createAsyncThunk

Type Changes

The alpha.0 release was broken when used with TypeScript. If you tried to declare a promise payload callback that took no parameters, our initial types forced TS to assume that the actual thunk action creator took a single arg of type never, making it impossible to dispatch correctly.

The types that we started from also were overly complex in how they tried to infer arguments and the return value of the promise callback.

We've reworked the createAsyncThunk types to correctly allow declaring a promise callback with no arguments, and simplified the type inference when there are arguments.

Action Payload Changes

Previously, the result of the promise callback and the arguments to the thunk action creator were passed together in the payload, as payload: {result, args}

This makes it harder to combine the thunk lifecycle actions and the EntityAdapter reducers together, as the reducers expect the real contents as simply action.payload. So, we've altered the action definitions so that the fulfilled action has the promise result as its action.payload, the rejected action has the error as action.error, and the thunk arguments are now in action.meta.args for all action types.

In addition, we wanted to add some kind of unique request ID to each set of lifecycle actions, to help tie them together if necessary. We now generate a unique ID per call to the thunk, using nanoid, and include that value as action.meta.requestId in each action dispatched out of that thunk call. It will also be passed in the options object that is the second argument to the promise callback.

Since we don't have any formal documentation yet, here is the signature of the call to the promise payload creator callback:

const result = (await payloadCreator(args, {
  dispatch,
  getState,
  extra,
  requestId
} as TA)) as Returned

and here are the internal action definitions:

const fulfilled = createAction(
    type + '/fulfilled',
    (result: Returned, requestId: string, args: ActionParams) => {
      return {
        payload: result,
        meta: { args, requestId }
      }
    }
  )

  const pending = createAction(
    type + '/pending',
    (requestId: string, args: ActionParams) => {
      return {
        payload: undefined,
        meta: { args, requestId }
      }
    }
  )

  const finished = createAction(
    type + '/finished',
    (requestId: string, args: ActionParams) => {
      return {
        payload: undefined,
        meta: { args, requestId }
      }
    }
  )

  const rejected = createAction(
    type + '/rejected',
    (error: Error, requestId: string, args: ActionParams) => {
      return {
        payload: undefined,
        error,
        meta: { args, requestId }
      }
    }
  )

Removal of TS 3.3 and 3.4 from CI

We've been trying to keep our TS types compatible with multiple TS versions, from 3.3 onwards. We've had to do a number of type workarounds to keep 3.3 compatibility, and it's become painful to keep that going.

Other libraries such as Immer have already jumped up to only supporting TS 3.7+.

For now, we're removing TS 3.3 and 3.4 from our CI runs, and will likely stop supporting them in our library types when 1.3.0 is released.

Example

I've put together a small CodeSandbox that demonstrates use of createSlice, createEntityAdapter, and createAsyncThunk working together in a test:

Redux Toolkit v1.3.0-alpha.1 APIs example

Changes

See PR #352: Port ngrx/entity and add createAsyncThunk

v1.3.0-alpha.0...v1.3.0-alpha.1

v1.2.5

13 Feb 04:06

Choose a tag to compare

This release tweaks the type definitions to fix an error where meta and error could not be typed when using the prepare notation of createSlice.

Changelog

  • correctly enforce meta and error types as returned from prepare (@phryneas - #350)

v1.2.4...v1.2.5

v.1.3.0-alpha.0

12 Feb 06:17

Choose a tag to compare

v.1.3.0-alpha.0 Pre-release
Pre-release

This release adds two new APIs: createEntityAdapter to help manage normalized state, and createAsyncThunk to abstract common data fetching behavior.

Note: this is an alpha release. These APIs are currently minimally tested, and the implementation details and API signatures may change. We hope that these APIs will be useful enough to officially release in the near future, and encourage users to try them out and give us feedback.

Please comment in issue #76: Create Async Action, issue #333: Consider adding logic for normalized state, and PR #352: Port ngrx/entity and add createAsyncThunk and let us know your thoughts!

This version is available as @reduxjs/toolkit@alpha on NPM, or @reduxjs/[email protected].

Changes

createEntityAdapter

The Redux docs have long advised storing data in a "normalized" state shape, which typically means keeping each type of item in a structure that looks like {ids: [], entities: {} }. However, the Redux core provides no APIs to help manage storing and updating your data using this approach. Many community libraries exist, with varying tradeoffs, but so far we haven't officially recommended any of them.

Caching data is a hard problem, and not one that we are interested in trying to solve ourselves. However, given that we do recommend this specific pattern, and that Redux Toolkit is intended to help simplify common use cases, we want to provide a minimal set of functionality to help users manage normalized state.

For this alpha release, we've specifically ported the @ngrx/entity library to work with Redux Toolkit, with some modifications.

The core API function is createEntityAdapter. It generates a set of reducer functions and selectors that know how to work with data that has been stored in that normalized {ids: [], entities: {} } format, and can be customized by passing in a function that returns the ID field for a given item. If you want to keep the item IDs in a sorted order, a comparison function can also be passed in.

The returned EntityAdapter object contains generated CRUD functions for manipulating items within that state, and generated selector functions that know how to read from that state. You can then use the generated CRUD functions and selectors within your own code.

Since this is an alpha, we don't have any API documentation yet. Please refer to the @ngrx/entity API docs for createEntityAdapter as a reference.

There is one very important difference between RTK's implementation and the original @ngrx/entity implementation. With @ngrx/entity, methods like addOne(item, state) accept the data argument first and the state second. With RTK, the argument order has been flipped, so that the methods look like addOne(state, item), and the methods can also accept a standard Redux Toolkit PayloadAction containing the data as the second argument. This allows them to be used as Redux case reducers directly, such as passing them in the reducers argument for createSlice.

Note: we've also updated these methods to use Immer internally. They already made immutable updates, but this simplified the implementation details. However, there is currently an issue we're seeing with nested uses of Immer behaving unexpectedly, so be careful when calling them inside a createSlice case reducer. Please see immerjs/immer#533 for details.

createAsyncThunk

The Redux docs have also taught that async logic should typically dispatch "three-phase async actions" while doing data fetching: a "start" action before the request is made so that loading UI can be displayed, and then a "success" or "failure" action to handle loading the data or showing an error message. Writing these extra action types is tedious, as is writing thunks that dispatch these actions and differ only by what the async request is.

Given that this is a very common pattern, we've added a createAsyncThunk API that abstracts this out. It accepts a base action type string and a callback function that returns a Promise as an argument, which is primarily intended to be a function that does a data fetch and returns a Promise containing the results. It then auto-generates the request lifecycle action types / creators, and generates a thunk that dispatches those lifecycle actions and runs the fetching callback.

From there, you can listen for those generated action types in your reducers, and handle loading state as desired.

Example Usage

This example demonstrates the basic intended usage of both createEntityAdapter and createAsyncThunk. It's incomplete, but hopefully shows enough of the idea to let you get started:

const usersAdapter = createEntityAdapter();

const fetchUsers = createAsyncThunk(
    "users/fetch",
    () => usersAPI.fetchAll()
);

// `fetchUsers` is now a typical thunk action creator, but also has
// four more action creators attached:
// pending, fulfilled, finished, rejected
// it will automatically dispatch those based on the promise lifecycle

const usersSlice = createSlice({
    name: "users",
    initialState: usersAdapter.getInitialState({loading: true}),
    reducers: {
        // createSlice will generate "users/userAdded" action types for these reducers
        userAdded: usersAdapter.addOne,
        userRemoved: usersAdapter.removeOne,
        // etc
    },
    extraReducers: {
        // would also want to handle the loading state cases, probably with a state machine,
        // using the lifecycle actions attached to fetchUsers
        [fetchUsers.fulfilled](state, action) {
            return usersAdapter.upsertMany(state, action.payload.result)
        }
    }
});

v1.2.4

09 Feb 23:16

Choose a tag to compare

This release tweaks the RTK-specific ThunkMiddleware type definition for better compatibility.

Changes

Thunk Middleware Type Definition

In v1.2.2, we improved our type definitions to correctly handle whether or not the thunk middleware was being used.

However, the tutorials and documentation recommended using a type like type AppThunk = ThunkAction<void, RootState, null, Action<string>>. The null for the extraArgument option no longer fit in with the changed types correctly and caused errors with code that was dispatching thunks in some cases.

We've tweaked the type definitions to better handle this, and updated the documentation to recommend using a type of type AppThunk = ThunkAction<void, RootState, unknown, Action<string>> instead.

Changelog

  • Better generic argument for default Thunk Middleware (@phryneas - #329)

v1.2.3...v1.2.4

v1.2.3

19 Jan 19:11

Choose a tag to compare

This release adds an option to the serializability-check middleware to allow ignoring specific state paths.

Changes

Allow Skipping Serializability Checks for State Paths

The redux-immutable-state-invariant middleware has an option for skipping checks for selected slices of state: https://github.com/leoasis/redux-immutable-state-invariant#api . However, our homegrown serializable-state-invariant middleware only allowed determining if a given value is serializable, and skipping specific actions, not skipping slices of state.

We've now added a ignoredPaths option to the serializability middleware that will force it to skip serializability checks for dot-separated keypaths within the state, like:

const store = configureStore({
    reducer: rootReducer,
    middleware: getDefaultMiddleware({
        serializabilityCheck: {
            ignoredPaths: ["testSlice.a", "testSlice.b.c"]
        }
    })
})

Note that we strongly advise against ever putting non-serializable values into the store state. This option is meant only as an escape hatch, such as working around libraries that do that anyway.

Changelog

v1.2.2...v1.2.3

v1.2.2

16 Jan 04:24

Choose a tag to compare

This releases fixes our dev UMD build, and improves type inference for dispatch based on provided middleware.

Changes

Dispatch and Middleware Typings

The Redux core types will modify the type of dispatch based on provided middleware, allowing it to accept parameters other than action objects and return other values. The redux-thunk middleware is an example of this.

RTK's configureStore and getDefaultMiddleware were not correctly picking up the types of the middleware, and were always assuming that redux-thunk was enabled at the type level even if the thunk middleware had been disabled.

This has been fixed, and the store should now correctly pick up the types of both the default and user-provided middleware.

Additional Type Re-Exports

RTK now re-exports the ThunkAction type from redux-thunk, and the Draft type from immer.

Dev UMD fixes

Our dev UMD build was broken due to the recent build configuration changes, and that has now been fixed. This means the sandbox in the Basic Tutorial should be working again.

Changelog

  • correctly infer dispatch from provided middlewares (@phryneas - #304)
  • export ThunkAction and Draft types (@phryneas - #312)
  • fix: UMD dev build by removing runtime usage of require statement (@alex-ketch - #317)

v1.2.1...v1.2.2

v1.2.1

26 Dec 19:57

Choose a tag to compare

Fixed a build tool misconfiguration that resulted in 1.2.0 leaving out the TS typings.d.ts declarations file. No other code changes from v1.2.0.

Changelog

  • Fix api-extractor command for cross-platform usage 016529f

v1.2.0...v1.2.1