Skip to content

Commit c4ad27c

Browse files
Fix multiple hydration error
1 parent 722e04c commit c4ad27c

File tree

3 files changed

+35
-8
lines changed

3 files changed

+35
-8
lines changed

src/reducer.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ export const createPersistedReducer: <
7878
) => {
7979
// Subscribe the reducer to be persisted
8080
Settings.subscribeSlice(reducerName);
81+
/**
82+
* A flag to ensure the rehydration process runs only once.
83+
* @internal
84+
*/
85+
let isHydrated = false;
8186

8287
/**
8388
* Creates a typed instance of the listener middleware's startListening function.
@@ -96,8 +101,10 @@ export const createPersistedReducer: <
96101
const reducer = createReducer(initialState, builder => {
97102
const b = new Builder(builder, UpdatedAtHelper.onStateChange.bind(null, reducerName));
98103
// Add a case to handle the rehydration of state from storage.
99-
b.builder.addCase(REHYDRATE.toString(), (_state, action: PayloadAction<Record<ReducerName, S> | null>): void | S => {
100-
if (action.payload?.[reducerName]) return action.payload[reducerName];
104+
b.addCase(REHYDRATE.toString(), (_state, action: PayloadAction<Record<ReducerName, S> | null>): void | S => {
105+
if (!isHydrated) {
106+
if (action.payload?.[reducerName]) return action.payload[reducerName];
107+
}
101108
});
102109
mapOrBuilderCallback(b);
103110
});
@@ -121,14 +128,15 @@ export const createPersistedReducer: <
121128
});
122129

123130
/**
124-
* Listens for the rehydration action to update the local timestamp,
125-
* ensuring the state isn't immediately re-saved.
131+
* Listens for the rehydration action to update the local timestamp and
132+
* set the `isHydrated` flag to prevent subsequent rehydrations.
126133
* @internal
127134
*/
128135
startAppListening({
129136
actionCreator: REHYDRATE,
130137
effect: () => {
131138
UpdatedAtHelper.onStateChange(reducerName);
139+
isHydrated = true;
132140
},
133141
});
134142

src/slice.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ export const createPersistedSlice: <
6060
) => {
6161
// Subscribe the slice to be persisted
6262
Settings.subscribeSlice(sliceOptions.name);
63+
/**
64+
* A flag to ensure the rehydration process runs only once per slice.
65+
* @internal
66+
*/
67+
let isHydrated = false;
6368

6469
/**
6570
* Creates a typed instance of the listener middleware's startListening function.
@@ -80,8 +85,10 @@ export const createPersistedSlice: <
8085
extraReducers: builder => {
8186
const b = new Builder(builder, UpdatedAtHelper.onStateChange.bind(null, sliceOptions.name));
8287
// Add a case to handle the rehydration of state from storage.
83-
b.builder.addCase(REHYDRATE.toString(), (_state, action: PayloadAction<Record<Name, SliceState> | null>): void | SliceState => {
84-
if (action.payload?.[sliceOptions.name]) return action.payload[sliceOptions.name];
88+
b.addCase(REHYDRATE.toString(), (_state, action: PayloadAction<Record<Name, SliceState> | null>): void | SliceState => {
89+
if (!isHydrated) {
90+
if (action.payload?.[sliceOptions.name]) return action.payload[sliceOptions.name];
91+
}
8592
});
8693
// Allow the user to add their own extra reducers.
8794
sliceOptions.extraReducers?.(b);
@@ -124,13 +131,14 @@ export const createPersistedSlice: <
124131
});
125132

126133
/**
127-
* Listens for the rehydration action to update the local timestamp,
128-
* ensuring the state isn't immediately re-saved.
134+
* Listens for the rehydration action to update the local timestamp and
135+
* set the `isHydrated` flag to prevent subsequent rehydrations.
129136
* @internal
130137
*/
131138
startAppListening({
132139
actionCreator: REHYDRATE,
133140
effect: () => {
141+
isHydrated = true;
134142
UpdatedAtHelper.onStateChange(sliceOptions.name);
135143
},
136144
});

src/store.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ export const configurePersistedStore: <
137137
}));
138138
}
139139

140+
/**
141+
* Overrides the original `replaceReducer` function to automatically
142+
* trigger rehydration after a new reducer has been injected.
143+
* @internal
144+
*/
145+
const _replaceReducer = persistedStore.replaceReducer;
146+
persistedStore.replaceReducer = (nR) => {
147+
_replaceReducer.call(persistedStore, nR);
148+
rehydrate();
149+
}
150+
140151
return new Promise<PersistedStore<S, A, M, E>>(async (resolve) => {
141152
try {
142153
const m = createListenerMiddleware();

0 commit comments

Comments
 (0)