@@ -38,6 +38,7 @@ SSR-safe Redux Toolkit middleware for localStorage persistence with selective sl
3838- [ Testing] ( #testing )
3939- [ Examples] ( #examples )
4040- [ TypeScript Support] ( #typescript-support )
41+ - [ Migration Guide] ( #migration-guide )
4142- [ Contributing] ( #contributing )
4243- [ License] ( #license )
4344
@@ -67,31 +68,36 @@ pnpm add lz-string # For compression
6768## Quick Start
6869
6970``` typescript
70- import { configureStore } from ' @reduxjs/toolkit'
71+ import { combineReducers , configureStore } from ' @reduxjs/toolkit'
7172import { createStorageMiddleware } from ' @gitbox/redux-storage-middleware'
7273
7374interface AppState {
7475 emails: EmailsState
7576 settings: SettingsState
7677}
7778
78- // Create middleware and API
79- const { middleware, api } = createStorageMiddleware <AppState >({
79+ // Create root reducer
80+ const rootReducer = combineReducers ({
81+ emails: emailReducer ,
82+ settings: settingsReducer ,
83+ })
84+
85+ // Create middleware, reducer, and API
86+ const { middleware, reducer, api } = createStorageMiddleware <AppState >({
87+ rootReducer , // Required: pass your root reducer
8088 name: ' my-app-state' ,
8189 slices: [' emails' , ' settings' ],
8290 version: 1 ,
8391})
8492
85- // Configure store
93+ // Configure store with returned reducer (already hydration-wrapped)
8694export const store = configureStore ({
87- reducer: {
88- emails: emailReducer ,
89- settings: settingsReducer ,
90- },
95+ reducer , // Use the returned reducer
9196 middleware : (getDefaultMiddleware ) =>
9297 getDefaultMiddleware ().concat (middleware ),
9398})
9499
100+ // Hydration happens automatically on client
95101// Export API for manual control
96102export { api as storageApi }
97103```
@@ -106,18 +112,18 @@ Creates the storage middleware and returns both the middleware and a control API
106112
107113#### Configuration Options
108114
109- | Option | Type | Default | Description |
110- | --------------- | --------------------------- | -------------- | ------------------------------------------------------ |
111- | ` name ` | ` string ` | ** required** | localStorage key name |
112- | ` slices ` | ` (keyof S)[] ` | ` undefined ` | State slices to persist (all if undefined) |
113- | ` partialize ` | ` (state: S) => Partial<S> ` | ` undefined ` | Custom state selector function |
114- | ` exclude ` | ` string[] ` | ` [] ` | Dot-notation paths to exclude (e.g., ` ['auth.token'] ` ) |
115- | ` version ` | ` number ` | ` 0 ` | Storage version for migrations |
116- | ` skipHydration ` | ` boolean ` | ` false ` | Skip automatic hydration on init |
117- | ` storage ` | ` StateStorage ` | ` localStorage ` | Custom storage backend |
118- | ` serializer ` | ` Serializer<T> ` | JSON | Custom serialization logic |
119- | ` merge ` | ` (persisted, current) => S ` | shallow | Merge strategy for hydration |
120- | ` migrate ` | ` (state, version) => S ` | identity | Version migration function |
115+ | Option | Type | Default | Description |
116+ | ------------- | --------------------------- | -------------- | ------------------------------------------------------ |
117+ | ` rootReducer ` | ` Reducer<S, AnyAction> ` | ** required** | Root reducer to wrap with hydration handling |
118+ | ` name ` | ` string ` | ** required ** | localStorage key name |
119+ | ` slices ` | ` (keyof S)[] ` | ` undefined ` | State slices to persist (all if undefined) |
120+ | ` partialize ` | ` (state: S) => Partial<S> ` | ` undefined ` | Custom state selector function |
121+ | ` exclude ` | ` string[] ` | ` [] ` | Dot-notation paths to exclude (e.g., ` ['auth.token'] ` ) |
122+ | ` version ` | ` number ` | ` 0 ` | Storage version for migrations |
123+ | ` storage ` | ` StateStorage ` | ` localStorage ` | Custom storage backend |
124+ | ` serializer ` | ` Serializer<T> ` | JSON | Custom serialization logic |
125+ | ` merge ` | ` (persisted, current) => S ` | shallow | Merge strategy for hydration |
126+ | ` migrate ` | ` (state, version) => S ` | identity | Version migration function |
121127
122128#### Performance Options
123129
@@ -141,8 +147,9 @@ Creates the storage middleware and returns both the middleware and a control API
141147
142148``` typescript
143149interface StorageMiddlewareResult <S > {
144- middleware: Middleware <object , S >
145- api: HydrationApi <S >
150+ middleware: Middleware <object , S > // Redux middleware
151+ reducer: Reducer <S , AnyAction > // Hydration-wrapped reducer (use this in configureStore)
152+ api: HydrationApi <S > // Control API
146153}
147154```
148155
@@ -265,7 +272,8 @@ const serializer = createCompressedSerializer<AppState>({
265272### Version Migrations
266273
267274``` typescript
268- const { middleware } = createStorageMiddleware <AppState >({
275+ const { middleware, reducer } = createStorageMiddleware <AppState >({
276+ rootReducer ,
269277 name: ' my-app' ,
270278 slices: [' settings' ],
271279 version: 2 ,
@@ -290,7 +298,8 @@ const { middleware } = createStorageMiddleware<AppState>({
290298``` typescript
291299import { shallowMerge , deepMerge } from ' @gitbox/redux-storage-middleware'
292300
293- const { middleware } = createStorageMiddleware <AppState >({
301+ const { middleware, reducer } = createStorageMiddleware <AppState >({
302+ rootReducer ,
294303 name: ' my-app' ,
295304 slices: [' emails' ],
296305 merge: deepMerge , // or shallowMerge (default)
@@ -340,7 +349,8 @@ export function StoreProvider({ children }) {
340349### Exclude Paths
341350
342351```typescript
343- const { middleware } = createStorageMiddleware<AppState >({
352+ const { middleware, reducer } = createStorageMiddleware<AppState >({
353+ rootReducer ,
344354 name: ' my-app' ,
345355 exclude: [
346356 ' auth.token' , // Skip sensitive data
@@ -353,7 +363,8 @@ const { middleware } = createStorageMiddleware<AppState>({
353363### Partialize Function
354364
355365` ` ` typescript
356- const { middleware } = createStorageMiddleware <AppState >({
366+ const { middleware, reducer } = createStorageMiddleware <AppState >({
367+ rootReducer ,
357368 name: ' my-app' ,
358369 partialize : (state ) => ({
359370 // Only persist specific nested data
@@ -488,7 +499,8 @@ A production-grade demo showing 5000+ email persistence:
488499**Configuration:**
489500
490501` ` ` typescript
491- const { middleware, api } = createStorageMiddleware <AppState >({
502+ const { middleware, reducer, api } = createStorageMiddleware <AppState >({
503+ rootReducer , // Pass your root reducer
492504 name: ' gmail-clone-state' ,
493505 slices: [' emails' ],
494506 version: 1 ,
@@ -517,7 +529,8 @@ Full TypeScript support with generic state typing:
517529
518530` ` ` typescript
519531// State type inference
520- const { middleware, api } = createStorageMiddleware <RootState >({
532+ const { middleware, reducer, api } = createStorageMiddleware <RootState >({
533+ rootReducer , // Required: pass your root reducer
521534 name: ' app' ,
522535 slices: [' user' , ' settings' ], // Type-checked against RootState keys
523536})
@@ -538,13 +551,75 @@ import {
538551 ACTION_HYDRATE_ERROR ,
539552 type StorageMiddlewareAction ,
540553} from ' @gitbox/redux-storage-middleware'
554+ ` ` `
555+
556+ > **Note:** ` withHydration ()` is deprecated. The returned ` reducer ` from ` createStorageMiddleware ()` is already hydration-wrapped.
557+
558+ ---
559+
560+ ## Migration Guide
561+
562+ ### Upgrading from v0.1.x to v0.2.x
563+
564+ **Breaking Changes:**
541565
542- // Use in reducers for hydration handling
543- import { withHydration } from ' @gitbox/redux-storage-middleware'
566+ 1. ` rootReducer ` is now a required parameter
567+ 2. ` skipHydration ` option has been removed (hydration is always enabled)
568+ 3. Return type now includes ` reducer ` which must be used in ` configureStore `
544569
545- const enhancedReducer = withHydration (rootReducer )
570+ **Before (v0.1.x):**
571+
572+ ` ` ` typescript
573+ import {
574+ createStorageMiddleware ,
575+ withHydration ,
576+ } from ' @gitbox/redux-storage-middleware'
577+
578+ const rootReducer = combineReducers ({
579+ settings: settingsReducer ,
580+ board: boardReducer ,
581+ })
582+
583+ const { middleware } = createStorageMiddleware ({
584+ name: ' my-app-state' ,
585+ slices: [' settings' ],
586+ skipHydration: false , // This option no longer exists
587+ })
588+
589+ const store = configureStore ({
590+ reducer: withHydration (rootReducer ), // Manual wrapper required
591+ middleware : (getDefault ) => getDefault ().concat (middleware ),
592+ })
593+ ` ` `
594+
595+ **After (v0.2.x):**
596+
597+ ` ` ` typescript
598+ import { createStorageMiddleware } from ' @gitbox/redux-storage-middleware'
599+
600+ const rootReducer = combineReducers ({
601+ settings: settingsReducer ,
602+ board: boardReducer ,
603+ })
604+
605+ const { middleware, reducer } = createStorageMiddleware ({
606+ rootReducer , // Required: pass your root reducer
607+ name: ' my-app-state' ,
608+ slices: [' settings' ],
609+ })
610+
611+ const store = configureStore ({
612+ reducer , // Use returned reducer (already hydration-wrapped)
613+ middleware : (getDefault ) => getDefault ().concat (middleware ),
614+ })
546615` ` `
547616
617+ **Key Benefits of the New API:**
618+
619+ - No more "silent failures" from forgetting to use ` withHydration ()`
620+ - Simpler, more intuitive API
621+ - Automatic hydration on client (no need to configure ` skipHydration ` )
622+
548623---
549624
550625## Contributing
0 commit comments