4 minutes read
The store manager supports persistence for state slices backed by reducers that implement the IHydratableReducer interface (hereafter named hydratable reducers).
// persistence is enabled for reducers that implements the re-hydrate and dehydrate functions
export interface IHydratableReducer<T> extends ISliceReducer<T> {
rehydrate(state: T, data: any): T;
dehydrate(state: T): any;
}
The manager detects whether a reducer supports persistence and automatically registers the proper event bindings and behaviors needed.
Specifically it will:
When a reducer is dynamically added or removed the hydration is honored accordingly: reducer/slice removal triggers dehydration while adding a new reducer/slice triggers hydration.
The dehydrated state data is serialized to JSON and saved into the storage with key "_redux_state_". The storage key is automatically cleared during initialization (application or developer should never access it).
The ready-to-be used reducers included in the library are also available with full state serialization/deserialization.
In general projects is indeed needed to implement custom persistence logic based on the data included in each state slice.
The persistent data is generated for each state slice whose reducer implements the "dehydrate" and "rehydrate" functions. The "dehydrate" and "rehydrate" functions should never throw any exception but should handle eventual issues internally. The store manager catches eventual exception and handle them skipping dehydration or using the coded initial state as a fallback.
The storage manager executes the "dehydrate" function of every hydratable reducer passing the current state object and collecting the result. The result is going to be serialized as JSON into the storage against the proper state slice key.
// let's usage a state to keep user authentication state and related details (they we don't want to persist)
export interface UserState {
authenticated: boolean;
details: User | undefined;
}
// we can create a UserReducer that only persists the authentication status (boolean)
export class UserReducer<T> extends ReduceableReducer<T> implements IHydratableReducer<T> {
// this function will be executed on beforeunload and it will receive the current slice state
dehydrate(state: UserState) {
// only the authenticated property will be persisted to storage
return { authenticated: state.authenticated };
}
rehydrate(state: UserState, data: any): T {
// ...
}
}
The storage manager executes the "rehydrate" function of every hydratable reducer during initialization passing the coded initial state and the data that was persisted into the storage.
// let's usage a state to keep user authentication state and related details (they we don't want to persist)
export interface UserState {
authenticated: boolean;
details: User | undefined;
}
// we can create a UserReducer that only persists the authentication status (boolean)
export class UserReducer<UserState> extends ReduceableReducer<UserState> implements IHydratableReducer<UserState> {
dehydrate(state: UserState): any {
// ...
}
// this function will be executed if any data was previously persisted against the storage
rehydrate(state: UserState, data: any): UserState {
// if the user was authenticated try to get the user details
if (data.authenticated === true) {
try {
const user: User = getUser();
return {
authenticated: true,
details: user
};
} catch (e) {
// something went wrong (maybe session expired?)
}
}
// we return an supposed-to-be-anonymous state in other cases
return state;
}
}
The storage manager automatically supports Redux state persistence to any Storage-compliant implementer class (Web Storage API). The persistence is activated for each slice that is backed by a reducer implementing the IHydratableReducer interface.
The persistence storage used by default is the window.localStorage but it can be overridden through the second argument of the "getInstance" function.
// initialize store, configure Redux and set sessionStorage as default backend for persistence
const storeManager = StoreManager.getInstance(initialReduxConfig, window.sessionStorage);
The store manager also provides convenient method to clear the persisted data and/or skip the next de-hydration thus enabling the clean up of the application state (for logout or user/context switching as examples).
// initialize store while skipping (and clearing) persisted state
const storeManager = StoreManager.getInstance(initialReduxConfig, window.localStorage, true);
// skip persistence for next dehydration cycle
storeManager.suspendStorage();