Skip to content

Commit d95246a

Browse files
Update documentation and add dump debounce
1 parent c4ad27c commit d95246a

File tree

5 files changed

+76
-29
lines changed

5 files changed

+76
-29
lines changed

src/reducer.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import {
66
import { Builder } from './extraReducersBuilder';
77
import { listenerMiddleware } from './middleware';
88
import Settings from './settings';
9-
import { NotFunction, ReducerWithInitialState, REHYDRATE } from './types';
9+
import { NotFunction, ReducerWithInitialState } from './types';
1010
import UpdatedAtHelper from './updatedAtHelper';
11-
import { writePersistedStorage } from './utils';
11+
import { REHYDRATE, writePersistedStorage } from './utils';
1212

1313
/**
1414
* A utility function that creates a persisted reducer. It wraps the standard
@@ -76,14 +76,38 @@ export const createPersistedReducer: <
7676
initialState: S | (() => S),
7777
mapOrBuilderCallback: (builder: ActionReducerMapBuilder<S>) => void,
7878
) => {
79-
// Subscribe the reducer to be persisted
79+
/**
80+
* Registers the reducer's name to the list of persisted slices.
81+
* This allows the persistence logic to identify which parts of the state to manage.
82+
*/
8083
Settings.subscribeSlice(reducerName);
84+
8185
/**
8286
* A flag to ensure the rehydration process runs only once.
8387
* @internal
8488
*/
8589
let isHydrated = false;
8690

91+
/**
92+
* A timeout variable to manage the debouncing of the storage write.
93+
* @internal
94+
*/
95+
let debounceTimeout: NodeJS.Timeout | null = null;
96+
97+
/**
98+
* Debounces the `writePersistedStorage` function to prevent excessive writes
99+
* during rapid state changes. The state is saved 100ms after the last change.
100+
* @param state - The current root state of the Redux store.
101+
* @param name - The name of the slice to persist.
102+
* @internal
103+
*/
104+
const onDump = (state: Record<ReducerName, S>, name: ReducerName) => {
105+
if (debounceTimeout) clearTimeout(debounceTimeout);
106+
debounceTimeout = setTimeout(() => {
107+
writePersistedStorage(state, name);
108+
}, 100);
109+
};
110+
87111
/**
88112
* Creates a typed instance of the listener middleware's startListening function.
89113
* @internal
@@ -117,13 +141,13 @@ export const createPersistedReducer: <
117141
startAppListening({
118142
predicate: (action) => {
119143
// Exclude the rehydrate action from triggering a save.
120-
if (action.type === REHYDRATE.toString()) return false;
144+
if (action.type === REHYDRATE.toString() || !Settings.isPersistenceEnabled) return false;
121145
return true;
122146
},
123147
effect: async (_action, { getState }) => {
124-
if (!await UpdatedAtHelper.shouldSave(reducerName) || !Settings.isPersistenceEnabled) return;
148+
if (!await UpdatedAtHelper.shouldSave(reducerName)) return;
125149
const state = getState();
126-
writePersistedStorage(state, reducerName);
150+
onDump(state, reducerName);
127151
},
128152
});
129153

src/slice.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import {
99
import { Builder } from './extraReducersBuilder';
1010
import { listenerMiddleware } from './middleware';
1111
import Settings from './settings';
12-
import { REHYDRATE } from './types';
1312
import UpdatedAtHelper from './updatedAtHelper';
14-
import { writePersistedStorage } from './utils';
13+
import { REHYDRATE, writePersistedStorage } from './utils';
1514

1615
/**
1716
* A wrapper around the standard RTK `createSlice()` function that adds
@@ -58,14 +57,38 @@ export const createPersistedSlice: <
5857
PeristedSelectors
5958
>,
6059
) => {
61-
// Subscribe the slice to be persisted
60+
/**
61+
* Registers the slice's name to the list of persisted slices.
62+
* This allows the persistence logic to identify which parts of the state to manage.
63+
*/
6264
Settings.subscribeSlice(sliceOptions.name);
65+
6366
/**
6467
* A flag to ensure the rehydration process runs only once per slice.
6568
* @internal
6669
*/
6770
let isHydrated = false;
6871

72+
/**
73+
* A timeout variable to manage the debouncing of the storage write.
74+
* @internal
75+
*/
76+
let debounceTimeout: NodeJS.Timeout | null = null;
77+
78+
/**
79+
* Debounces the `writePersistedStorage` function to prevent excessive writes
80+
* during rapid state changes. The state is saved 100ms after the last change.
81+
* @param state - The current root state of the Redux store.
82+
* @param name - The name of the slice to persist.
83+
* @internal
84+
*/
85+
const onDump = (state: Record<Name, SliceState>, name: Name) => {
86+
if (debounceTimeout) clearTimeout(debounceTimeout);
87+
debounceTimeout = setTimeout(() => {
88+
writePersistedStorage(state, name);
89+
}, 100);
90+
};
91+
6992
/**
7093
* Creates a typed instance of the listener middleware's startListening function.
7194
* @internal
@@ -106,7 +129,7 @@ export const createPersistedSlice: <
106129
effect: (_action, { getState }) => {
107130
if (Settings.isPersistenceEnabled) {
108131
const state = getState();
109-
writePersistedStorage(state, sliceOptions.name);
132+
onDump(state, sliceOptions.name);
110133
}
111134
},
112135
});
@@ -120,13 +143,13 @@ export const createPersistedSlice: <
120143
startAppListening({
121144
predicate: (action) => {
122145
// Exclude the slice's own actions (already handled) and the rehydrate action.
123-
if (action.type === REHYDRATE.toString() || action.type.startsWith(`${slice.name}/`)) return false;
146+
if (action.type === REHYDRATE.toString() || action.type.startsWith(`${slice.name}/`) || !Settings.isPersistenceEnabled) return false;
124147
return true;
125148
},
126149
effect: async (_action, { getState }) => {
127-
if (!await UpdatedAtHelper.shouldSave(sliceOptions.name) || !Settings.isPersistenceEnabled) return;
150+
if (!await UpdatedAtHelper.shouldSave(sliceOptions.name)) return;
128151
const state = getState();
129-
writePersistedStorage(state, sliceOptions.name);
152+
onDump(state, sliceOptions.name);
130153
},
131154
});
132155

src/store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Action, configureStore, ConfigureStoreOptions, createDynamicMiddleware, createListenerMiddleware, StoreEnhancer, Tuple, UnknownAction } from "@reduxjs/toolkit";
22
import { listenerMiddleware } from "./middleware";
33
import Settings from "./settings";
4-
import { Enhancers, ExtractDispatchExtensions, Middlewares, PersistedStore, REHYDRATE, RESUME_PERSISTANCE, StorageHandler, ThunkMiddlewareFor } from "./types";
4+
import { Enhancers, ExtractDispatchExtensions, Middlewares, PersistedStore, StorageHandler, ThunkMiddlewareFor } from "./types";
55
import UpdatedAtHelper from "./updatedAtHelper";
6-
import { clearPersistedStorage, getStoredState } from "./utils";
6+
import { clearPersistedStorage, getStoredState, REHYDRATE, RESUME_PERSISTANCE } from "./utils";
77

88
/**
99
* A friendly encapsulation of the standard RTK `configureStore()` function

src/types.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
Action,
33
ActionCreatorInvariantMiddlewareOptions,
4-
createAction,
54
EnhancedStore,
65
ImmutableStateInvariantMiddlewareOptions,
76
Middleware,
@@ -35,19 +34,6 @@ export interface StorageHandler {
3534
removeItem: (key: string) => Promise<void> | void;
3635
}
3736

38-
/**
39-
* Action dispatched to rehydrate the store with persisted state.
40-
* @internal
41-
*/
42-
export const REHYDRATE = createAction<Record<string, unknown>>('@@INIT-PERSIST');
43-
44-
/**
45-
* Action dispatched to trigger an immediate re-save of the current state
46-
* after persistence has been resumed.
47-
* @internal
48-
*/
49-
export const RESUME_PERSISTANCE = createAction<void>('@@RESUME-PERSIST');
50-
5137
// --- Internal RTK Types ---
5238
// These types are re-exported or re-defined from Redux Toolkit's internal
5339
// types to ensure proper type inference in `configurePersistedStore`.

src/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
1+
import { createAction } from "@reduxjs/toolkit";
12
import Settings from "./settings";
23
import UpdatedAtHelper from "./updatedAtHelper";
34

5+
/**
6+
* Action dispatched to rehydrate the store with persisted state.
7+
* @internal
8+
*/
9+
export const REHYDRATE = createAction<Record<string, unknown>>('@@INIT-PERSIST');
10+
11+
/**
12+
* Action dispatched to trigger an immediate re-save of the current state
13+
* after persistence has been resumed.
14+
* @internal
15+
*/
16+
export const RESUME_PERSISTANCE = createAction<void>('@@RESUME-PERSIST');
17+
418
/**
519
* Generates the unique storage key for a given slice name.
620
* @param name - The name of the slice.

0 commit comments

Comments
 (0)