Skip to Content

persist

Caro-Kann allows global state to be stored in local storage, session storage, and cookies. This feature is especially important for improving user experience and is suitable for values that need to persist even after a page refresh or session termination, such as the theme settings of a webpage.

const useStore = create( persist(initialState, persistOptions) )

When storing global state in Caro-Kann, the state is stored alongside a version. This allows the application to easily transform or disregard data from previous versions if the state structure changes.

type Theme = "light" | "dark"; const useStore = create<Theme>( persist( "light", { local: "theme", // session: "theme", // cookie: "theme", } ) );
KeyValue
theme{"state":"light","version":0}

For example, suppose the theme needs to support not only background color but also font size. Caro-Kann handles this by using the migrate array. When a migrate object is present, Caro-Kann automatically checks for version differences whenever a client connects to the service. If the client’s state is not up to date, it calls the appropriate function(s) in the array to transform the old state into the updated version, ensuring the client always works with the latest schema.

type Theme = { color: "light" | "dark", fontSize: number }; const useStore = create<Theme>( persist( { color: "light", fontSize: 16 }, { local: "theme", migrate: [ (prev: "light" | "dark") => ({ color: prev, fontSize: 16 }) ], } ) );
KeyValue
theme{"state":{"color":"dark","fontSize":16},"version":1}

We successfully used migrate to update version 0 to version 1. However, a few weeks later, a senior developer comes by and requests that the font state property be renamed to font-size. Since migrate only runs when a client connects to the service, any user who hasn’t connected yet still retains version 0 of the state. Therefore, we now need to handle both version 0 and version 1 during the migration process.

But don’t worry! To address this, the migrate array is structured like a pipeline.

type Theme = { color: "light" | "dark", ["font-size"]: number }; const useStore = create<Theme>( persist( { color: "light", ["font-size"]: 16 }, { local: "theme", migrate: [ (prev: "light" | "dark") => ({ color: prev, fontSize: 16 }), (prev: { color: "light" | "dark", fontSize: number }) => ({ color: prev.color, ["font-size"]: prev.fontSize }) ], } ) );
KeyValue
theme{"state":{"color":"dark","font-size":16},"version":2}

With the help of TypeScript, the migrate pipe enforces two key conditions. First, the return type of a function at a given index must match the parameter type of the function at the next index. This ensures that each step in the migration process is type-safe and properly connected, allowing TypeScript to catch potential type mismatches at compile time and prevent runtime errors. Additionally, the return type of the final function in the pipe must exactly match the current global state type used by the application, ensuring that the final migrated state is fully compatible with the rest of the application logic.

Last updated on