Skip to main content

Persisting the state

Finally, let's add a persistor to save the state to the local device disk. This is done during the store creation:

const store = createStore<State>({
initialState: State.initialState,
showUserException: userExceptionDialog,
persistor: persistor, // Here!
});

The saved state is loaded when the store is created. Until it finishes loading, the store state is the initialState. Dispatching an action before that throws an error, since the saved state would overwrite its changes. If you need to dispatch actions when the app starts, first wait with await store.ready().

In components, use the useIsStoreReady() hook to show a loading state and disable buttons until the store is ready:

const isReady = useIsStoreReady();
if (!isReady) return <CircularProgress />;

Read more in Persistor.

You can implement your own persistor, as long as it follows the abstract Persistor interface, provided by Kiss.

Adding a persistor allows the user to reload the page, or close the browser and reopen it later, without losing the todo list.

ClassPersistor​

Kiss comes out of the box with the ClassPersistor that implements the Persistor interface. It serializes ES6 classes out of the box, and it will persist the whole state of your application.

To use it, you must provide:

  • loadSerialized: a function that returns the serialized state.
  • saveSerialized: a function that saves the serialized state.
  • deleteSerialized: a function that deletes the serialized state.
  • classesToSerialize: an array of all the custom classes that are part of your state.

In more detail, here's the ClassPersistor constructor signature:

constructor(

// Returns the serialized state.
// It should return a Promise that resolves to the serialized state,
// or to null if the state is not yet persisted.
public loadSerialized: () => Promise<string | null>,

// Saves the given serialized state.
// It should return a Promise that resolves when the state is saved.
public saveSerialized: (serialized: string) => Promise<void>,

// Deletes the serialized state.
// It should return a Promise that resolves when the state is deleted.
public deleteSerialized: () => Promise<void>,

// List here all the custom classes that are part of your state, directly
// or indirectly. Note: You don't need to list native JavaScript classes.
public classesToSerialize: Array<ClassOrEnum>
)

For our Todo List app, this is the complete code:

let persistor = new ClassPersistor<State>(

// loadSerialized
async () => window.localStorage.getItem('state'),

// saveSerialized
async (serialized: string) => window.localStorage.setItem('state', serialized),

// deleteSerialized
async () => window.localStorage.clear(),

// classesToSerialize
[State, TodoList, TodoItem, Filter]
);

Try it yourself​

Add some items to the list, mark some of them as completed, and then reload the page. You should see the same items and their completed status.

Implementing your own serializer​

If instead of using the provided ClassPersistor you decide to implement your own serializer, the following information may be useful for you.

In JavaScript there are two types of objects:

  • Plain (literal) objects - Instances of the Object class. Sometimes they are called literal objects, when created via the {} notation.

  • Class (constructor) objects - Instances of classes with own defined constructor, properties and methods. Usually you define them via class notation.

If you are loading a JSON from your backend, after you JSON.parse() it, you have a plain JavaScript object, not an instance of a class.

These packages will help you transform plain JavaScript objects into ES6 classes:

  • esserializer - The ClassPersistor uses an adapted copy of this package, which is included in Kiss. ESSerializer is a JavaScript serialization and deserialization utility, that helps you serialize classes to JSON, and then deserialize them back to class objects, with all properties and methods, recursively. No eval is used, which means it doesn't introduce any security risks.

  • class-transformer - Allows you to transform plain JavaScript objects into some class object and versa, and also serialize and deserialize them.