store icon indicating copy to clipboard operation
store copied to clipboard

🐞[BUG]: NGXS Migration to existing local storage state

Open MadEddieFFS opened this issue 1 month ago • 2 comments

We have our state persisted to local storage and currently do not use migrations. This state therefore does not have a version tag.

As we must specify a version in the migration as a string|number it seems the migration never runs. I would like to initially change a defaulted value in the state with this migration

export const callHistoryMigrations = [ { version: 0, key: 'callhistory', versionKey: 'version', migrate: (state:any) => { return { ...state, missedCallsCount: 100, version: 1 }; } } ];

but it doesn't run. I added the version field to my state model and the defaults.

Looking at the code in ngxs-storage the getValue would never work as it would be comparing version 0 to undefined:

const versionMatch = strategy.version === getValue(storedValue, strategy.versionKey || 'version');

which is: const versionMatch = 0 === undefined;

MadEddieFFS avatar Nov 04 '25 11:11 MadEddieFFS

🐞[BUG]: NGXS Migration to existing local storage state

We have our state persisted to local storage and currently do not use migrations. This state therefore does not have a version tag.

As we must specify a version in the migration as a string|number it seems the migration never runs. I would like to initially change a defaulted value in the state with this migration

export const callHistoryMigrations = [ { version: 0, key: 'callhistory', versionKey: 'version', migrate: (state:any) => { return { ...state, missedCallsCount: 100, version: 1 }; } } ];

but it doesn't run. I added the version field to my state model and the defaults.

Looking at the code in ngxs-storage the getValue would never work as it would be comparing version 0 to undefined:

const versionMatch = strategy.version === getValue(storedValue, strategy.versionKey || 'version');

which is: const versionMatch = 0 === undefined;

Hey @MadEddieFFS

Here is my answer:

The issue you're encountering with NGXS migrations not running for your local storage state is due to the absence of a version tag in your existing state. When you attempt to run a migration, the NGXS Storage Plugin checks for a version match using the versionKey specified in your migration strategy. If your stored state does not have this version key, the comparison will result in undefined, causing the migration not to execute.

Here's a breakdown of the problem and a potential solution:

  1. Version Key Absence: Your existing state in local storage lacks a version key. The migration logic in NGXS checks if the current version matches the specified version in the migration strategy. Since your state does not have a version, this check fails (0 === undefined).

  2. Migration Strategy: Your migration strategy is defined as follows:

    export const callHistoryMigrations = [
      {
        version: 0,
        key: 'callhistory',
        versionKey: 'version',
        migrate: (state: any) => {
          return {
            ...state,
            missedCallsCount: 100,
            version: 1
          };
        }
      }
    ];
    

    This strategy will not run because the version field is missing in your stored state.

  3. Solution: To resolve this, you need to ensure that your stored state includes a version key. You can manually update your local storage to include a version key with a default value (e.g., version: 0). This will allow the migration to recognize the version and execute the migration logic.

  4. Code Adjustment: Before running your application, you might need to manually set the version in your local storage for the first time:

    const initialState = JSON.parse(localStorage.getItem('callhistory') || '{}');
    initialState.version = 0;
    localStorage.setItem('callhistory', JSON.stringify(initialState));
    

By ensuring that your state includes a version key, the migration should execute as expected.


Trust Score: 🟢 87%

Sources:

👀 View on Gurubase

Tag @gurubase to ask me a question.

gurubase[bot] avatar Nov 04 '25 11:11 gurubase[bot]

Thanks - thats great. I have leveraged the ngxsOnInit to add the version to the store if it is null | undefined

 ngxsOnInit(ctx: StateContext<any>): void {
    const state = ctx.getState();
    const version = state.version;

    if (version === undefined || version === null) {
      state.version = 0;
    }
  } 

However if a user "skips" a version it would mean that the ngxsOnInit would need to also apply the changes which are in the migration and any subsequent ones it missed. It therefore negates the migration script entirely. Something like:

 ngxsOnInit(ctx: StateContext<any>): void {
    const state = ctx.getState();
    const version = state.version;

    if (version === undefined || version === null) {
      state.version = 0;

      // New to version 1
      state.newField = 100;

        // New to version 2
       state.secondNewField = 10;
    }
  } 

Can the migration be triggered programatically? Perhaps after ngxsOnInit discovers that the version field is undefined in the store ?

MadEddieFFS avatar Nov 06 '25 14:11 MadEddieFFS