Intro
Redux Toolkit
On December 4, 2023, Mark Erikson announced that Redux Toolkit 2.0 has been released!
This change is considered a major version of the library and comes with breaking changes because, as the core team declared, it has been 4+ years since they released Redux tool kit, and they needed to clean up the code and standardize the way Redux apps are written. One of these changes is you no longer need to import the core redux
library, RTK is a wrapper of it. In this article, we are going to focus on the main breaking changes and new features in RTK 2.0
Some general notes on the change
- Standalone getDefaultMiddleware and getType removed
- The package definitions now include an exports field to specify which artifacts to load, with a modern ESM build
- UMD has been dropped
- Typescript rewrite
createSlice and createReducer
One of the major changes is object syntax for createSlice.extraReducers and createReducer removed. In RTK 2.0, Redux team has opted to remove the 'object' form for createReducer and createSlice.extraReducers, as the 'builder callback' form offers greater flexibility and is more TypeScript-friendly, making it the preferred choice for both APIs
So an example like this:
// Creating an action creator function called 'itemAdded'
const itemAdded = createAction('items/itemAdded');
// Using 'createReducer' function to define reducers
createReducer(initialState, {
[todoAdded]: (state, action) => {
},
});
// Using 'createSlice' function to define a slice of state and its reducers
createSlice({
name, // Name of the slice (assumed to be defined elsewhere)
initialState, // Initial state of the slice (assumed to be defined elsewhere)
reducers: {},
// Defining extra reducers for actions outside of this slice
extraReducers: {
// Defining a reducer for the 'itemAdded' action
[itemAdded]: (state, action) => {},
},
});
Should be like this:
// Using 'createReducer' function to define reducers
createReducer(initialState, (builder) => {
// Using 'addCase' method of the builder to add a case reducer for the 'itemAdded' action
builder.addCase(itemAdded, (state, action) => {});
});
// Using 'createSlice' function to define a slice of state and its reducers
createSlice({
name, // Name of the slice (assumed to be defined elsewhere)
initialState, // Initial state of the slice (assumed to be defined elsewhere)
reducers: {},
// Defining extra reducers for actions outside of this slice
extraReducers: (builder) => {
// Using 'addCase' method of the builder to add a case reducer for the 'itemAdded' action
builder.addCase(itemAdded, (state, action) => {});
},
});
Middlewares
Now the configureStore.middleware
must be a callback. That’s because before to add a middleware to the Redux store, you had to add it to the array of configureStore.middleware
, but this is turning off the default middleware. Now it’s a callback function that should return an array with middlewares, the default middleware is being passed in the parameters of the callback function so the developer can include it or not in the middleware array like this:
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) => {
// This will not run the default Middleware and run myCustomMiddleware instead
return [myCustomMiddleware]
},
})
Enhancers
Store enhancers are a formal mechanism for adding capabilities to Redux itself like adding some additional capabilities to the store. It could change how reducers process data, or how dispatch works. Most people will never need to write one. But similar to the middlewares, enhancers also must be a callback function that receives getDefaultEnhancers
and should return enhancers back.
const store = configureStore({
reducer,
enhancers: (getDefaultEnhancers) => {
return getDefaultEnhancers({
autoBatch: { type: 'tick' },
}).concat(myEnhancer)
},
})
autoBatchEnhancer
In version 1.9.0, the Redux team introduced a new autoBatchEnhancer that, when multiple "low-priority" actions are dispatched consecutively. This enhancement aims to optimize performance by reducing the impact of UI updates, typically the most resource-intensive part of the update process. While RTK Query automatically marks most of its internal actions as "low-pri" users need to add the autoBatchEnhancer to the store for this feature to take effect. To simplify this process, configureStore has been updated to include the autoBatchEnhancer in the store setup by default, allowing users to benefit from improved performance without manual store configuration adjustments.
AnyAction and UnknownAction
The Redux TS types have historically exported an AnyAction
type that lacks precise type safety, allowing for loose typology like console.log(action.whatever)
. However, to promote safer typing practices, they've introduced UnknownAction
, which treats all fields beyond action.type
as unknown
, nudging users towards creating type guards with assertions for better type safety when accessing action objects. UnknownAction
is now the default action object type in Redux, while AnyAction
remains available but deprecated.
RTK Query behavior
They've addressed issues with RTK Query, such as problems with dispatch(endpoint.initiate(arg, {subscription: false}))
and incorrect resolution timing for triggered lazy queries, by reworking RTKQ to consistently track cache entries. Additionally, they've introduced internal logic to delay tag invalidation for consecutive mutations, controlled by the new invalidationBehavior
flag on createApi
, with the default being 'delayed'. In RTK 1.9, the subscription status has been optimized syncing to the Redux state for performance, primarily for display in the Redux DevTools "RTK Query" panel.
Conclusion
This version is a major version of Redux Toolkit and it has a lot of breaking changes but what is important its source code has been cleaned and rewritten in TS will improve the developer experience for sure and on the final user experience as well, We discussed here only the headlines and the most used features but of course you can find all of the changes on The official Redux Github Releases page