robinweser / alveron

Tiny (0.8kb) Elm-inspired state management for React
https://alveron.js.org
MIT License
74 stars 2 forks source link

Async Persistence #91

Open robinweser opened 1 month ago

robinweser commented 1 month ago

Right now, the persistence middleware only supports sync storages such as localStorage or sessionStorage. If we'd want to use e.g. IndexedDB it would fail due to the sync nature.

robinweser commented 1 month ago

Could work like this:

import { Middleware, MiddlewareContext } from "alveron";

type SyntheticStorage<T = any> = {
  getItem: (key: string) => Promise<T>;
  setItem: (key: string, value: T) => void;
};
type Config<T> = {
  key: string;
  getStorage: () => Storage | SyntheticStorage;
  actions?: Array<string>;
  onHydrated?: (data?: T) => void;
  encode?: (data?: T) => any;
  decode?: (data: any) => T;
};

export default function persistence<T>({
  key,
  getStorage,
  actions,
  onHydrated,
  encode = JSON.stringify,
  decode = JSON.parse,
}: Config<T>): Middleware {
  function middleware(nextState: any, { action }: MiddlewareContext) {
    if (actions && Array.isArray(actions) && !actions.includes(action)) {
      return nextState;
    }

    const storage = getStorage();

    if (storage) {
      try {
        storage.setItem(key, encode(nextState));
      } catch (e) {}
    }

    return nextState;
  }

  function effect(setState: any) {
    const storage = getStorage();

    if (storage) {
      const isAsync = storage.getItem.constructor.name === "AsyncFunction";

      async function getData() {
        if (isAsync) {
          return await storage.getItem(key);
        } else {
          return storage.getItem(key);
        }
      }

      async function hydrate() {
        const data = await getData();

        let parsedData;
        if (data) {
          try {
            parsedData = decode(data);
            setState(parsedData);
          } catch (e) {}
        }

        if (onHydrated) {
          onHydrated(parsedData);
        }
      }

      hydrate();
    }
  }

  return {
    middleware,
    effect,
  };
}