I am working on a React Native app. I have a combination of reducers set up together using persitCombineReducers.
One of the slices had a revamp and now has a new structure.
This is the old structure :
interface OldAuthState {
token?: string;
expiresAt?: string;
customer?: CustomerAuthFragment;
numberOfTimeAskedForData?: number;
lastDateAskedForData?: Date;
dismissedQuizQuestions: string[];
}
This is the new structure that I want to migrate to:
interface AuthToken {
token: string;
expiresAt: string;
}
interface DataInfo {
numberOfTimeAskedForData: number;
lastDateAskedForData?: Date;
}
export interface AuthState {
accessToken?: AuthToken;
customer?: CustomerAuthFragment;
dataInfo: DataInfo;
dismissedQuizQuestions: string[];
}
After some research online, I found out that using migrations and createMigrate function from redux-persist is the way to go.
Thanks to copilot I ended up with this function
const migrations = {
1: (state: PersistedState) => {
// Extract the old auth state.
const oldAuthState = (state as any).auth as OldAuthState;
// Initialize the new auth state based on the old one.
const newAuthState: AuthState = {
dataInfo: {
numberOfTimeAskedForData: oldAuthState.numberOfTimeAskedForData ?? 0,
lastDateAskedForData: oldAuthState.lastDateAskedForData,
},
accessToken: {
token: oldAuthState.token ?? '',
expiresAt: oldAuthState.expiresAt ?? '',
},
customer: oldAuthState.customer,
dismissedQuizQuestions: oldAuthState.dismissedQuizQuestions,
};
// Return the new state.
return {
...state,
// Replace the old auth state with the new one.
auth: newAuthState,
};
},
};
const migrate = createMigrate(migrations, { debug: process.env.NODE_ENV === 'development' });
I am aware that copilot gives me some naive code on how to do this. Also, I am unsure about a few points:
Online, I only found examples on how to add a new value to the state structure and not updating the whole state. Plus I don't find any documentation about this on redux website.
How will it know which persisted state is being provided during the migration?
This where the version
of the persist configuration comes into play.
{ key: string, storage: Object, version?: number, // the state version as an integer (defaults to -1) blacklist?: Array<string>, whitelist?: Array<string>, migrate?: (Object, number) => Promise<Object>, transforms?: Array<Transform>, throttle?: number, keyPrefix?: string, debug?: boolean, stateReconciler?: false | StateReconciler, serialize?: boolean, writeFailHandler?: Function, }
You effectively version the state you are persisting, and the migrations are applied, in order, from the version of the persisted state to the current version of the configuration. Example, if there are migrations for versions 0, 1, 2, 3, and 4, the currently persisted state is version 1, and the current version in the configuration is version 4, then migrations 2, 3, and 4 are applied, in that order, to update the state to the current version.
How to map the former state structure with the new one?
This one is really up to you to handle, however you need. You can add new properties (with valid initial values), delete properties that are no longer needed, and move state around as you deem necessary when the state shape changes.
From what I can tell of your code/logic it appears to accomplish this. I see no issue with the code other than state
is already the state
type and should likely already have the auth
property defined. I'd suggest versioning the types so if/when there are future migrations in the same areas of state that it's easy to handle type declarations.
Example:
interface AuthState_V0 {
token?: string;
expiresAt?: string;
customer?: CustomerAuthFragment;
numberOfTimeAskedForData?: number;
lastDateAskedForData?: Date;
dismissedQuizQuestions: string[];
}
to
interface AuthToken {
token: string;
expiresAt: string;
}
interface DataInfo {
numberOfTimeAskedForData: number;
lastDateAskedForData?: Date;
}
export interface AuthState_V1 {
accessToken?: AuthToken;
customer?: CustomerAuthFragment;
dataInfo: DataInfo;
dismissedQuizQuestions: string[];
}
const migrations = {
1: (state: PersistedState) => {
// Extract the old auth state.
const oldAuthState = state.auth as AuthState_V0;
// Initialize the new auth state based on the old one.
const newAuthState: AuthState_V1 = {
dataInfo: {
numberOfTimeAskedForData: oldAuthState.numberOfTimeAskedForData ?? 0,
lastDateAskedForData: oldAuthState.lastDateAskedForData,
},
accessToken: {
token: oldAuthState.token ?? '',
expiresAt: oldAuthState.expiresAt ?? '',
},
customer: oldAuthState.customer,
dismissedQuizQuestions: oldAuthState.dismissedQuizQuestions,
};
// Return the new state.
return {
...state,
// Replace the old auth state with the new one.
auth: newAuthState,
};
},
};