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 returnstrue
, the case reducer will run. Multiple matchers may be added tocreateReducer
, and all matchers that returntrue
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 ofcreateEntityAdapter
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)