robinweser / alveron

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

Async Persistence #91

Open robinweser opened 1 week ago

robinweser commented 1 week 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 week 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,
  };
}