quarrant / mobx-persist-store

Persist and rehydrate observable properties in mobx store.
268 stars 14 forks source link

[Question] Can I serialize and deserialize class instances instead of objects? #92

Closed nathan-alden-sr closed 8 months ago

nathan-alden-sr commented 2 years ago

I define entity classes like so:

import { makeAutoObservable } from "mobx";
import { nanoid } from "nanoid";

export default class Todo {
  public id;
  public title;
  public isFinished = false;

  public constructor(title: string) {
    makeAutoObservable(this);

    this.id = nanoid();
    this.title = title;
  }

  public toggle() {
    this.isFinished = !this.isFinished;
  }
}

and a store for them like so:

import { makeAutoObservable } from "mobx";
import { makePersistable } from "mobx-persist-store";
import Todo from "../entities/Todo";
import localForageEntityStorageController from "./LocalForageEntityStorageController";

class TodoStore {
  public todos: Todo[] = [];

  public constructor() {
    makeAutoObservable(this);

    makePersistable(
      this,
      {
        name: "todos",
        properties: ["todos"],
        storage: localForageEntityStorageController,
        stringify: false
      },
      {
        delay: 200
      }
    );
  }
}

export default new TodoStore();

Unfortunately, mobx-persist-store doesn't seem to support this because there doesn't seem to be an elegant way for me to e.g. use a library like class-transformer to convert the array of plain objects stored by the store into an array of class instances. I'm trying to hack my own StorageController but I'm running into issues with generic method signatures.

Is there a way to accomplish this?

nathan-alden-sr commented 2 years ago

I managed to get this working:

getItem<T>(key: string) {
  return localForage.getItem<T>(key).then(value => {
    if (isNil(value)) {
      return value;
    }

    const valueAsRecord = value as Record<string, any>;
    let deserializedValue;

    switch (key) {
      case "todos":
        deserializedValue = {
          [key]: valueAsRecord.todos.map((a: unknown[]) => plainToInstance(Todo, a))
        };
        break;
      default:
        throw new Error(`Unexpected entity key ${key}`);
    }

    return deserializedValue as unknown as T;
  });
}

This works in TypeScript strict mode. However, I am still wondering if there is a better solution.

quarrant commented 2 years ago

@nathan-alden-sr I added serializable properties in version 1.1.2, please try it https://github.com/quarrant/mobx-persist-store#example-with-serializableproperties