Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing schema of state - guidance on versioning/migrations? #147

Open
kylekampy opened this issue Apr 26, 2023 · 0 comments
Open

Changing schema of state - guidance on versioning/migrations? #147

kylekampy opened this issue Apr 26, 2023 · 0 comments

Comments

@kylekampy
Copy link

Hello! Thanks so much for creating this utility! It is so useful and I very much appreciate it.

I ran into a small problem, which is definitely self-inflicted I just didn't realize I was going to encounter it.

I've got state persisted to local storage. There are various top-level keys within the state that it's writing to storage. I'd like to add an additional key. As an example, currently I have:

createStore(
    {
      thing1: getThing1InitialState(), // returns some object with default values
      thing2: getThing2InitialState(),
    },
);

And I want to add a new thing. My code now looks like:

createStore(
    {
      thing1: getThing1InitialState(),
      thing2: getThing2InitialState(),
      thing3: getThing3InitialState(), // <-- new
    },
);

Then my components have:

const { state } = useStateMachine();

const propertyOfThing3 = state.thing3.foo; // unexpected -- cannot access property 'foo' of undefined

Because I already had a JSON bit of state written to local storage before releasing a version with thing3, the JSON blob is parsed and used as state on app load through the eventual calls to updateStore https://github.com/beekai-oss/little-state-machine/blob/master/src/logic/storeFactory.ts#L20. Because that older version of state didn't have thing3, it's just not included in there, and there doesn't appear to be logic in little-state-machine to "fill in the blanks" with default values. Then state.thing3 is undefined and things crash.

The way I solved this for myself was in assuming any of the pieces of state could be undefined. I have my GlobalState type defined like:

declare module 'little-state-machine' {
  interface GlobalState {
    thing1?: Thing1Type;
    thing2?: Thing2Type;
    thing3?: Thing3Type;
  }
}

And then my actions all take special precaution to assume they may be working with nested state that isn't yet defined:

export function updateThing3Foo(state: GlobalState, payload: {
  newFoo: string,
}): GlobalState {
  return {
    ...state,
    thing3: {
      ...getThing3InitialState(), // <-- in case we've added new properties, or this whole state hasn't yet been written/parsed
      ...state.thing3, // <-- in case there was already state, and we don't want to lose any of the other properties that were here
      foo: payload.newFoo,
    },
  };
}

I figured I'd write this out in case anyone else has also encountered this problem and is looking for some kind of solution that seems to work. But I would also love to hear from others on where I went wrong. How are others adding to their state over time without crashes on release? Does anyone have a migration or versioning strategy they use to keep their typing of state and actual state consistent?

Thanks so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant