JSFiddle - React, Tailwind, and code Playground

by Jason Miller

HTML

<script src="https://unpkg.com/@preact/signals-core@1.4.0/dist/signals-core.min.js"></script>

TypeScript

setTimeout(() => {
const count = persistedSignal(0, 'count');

const decrement = document.createElement('button');
decrement.onclick = () => count.value--;
decrement.textContent = '-';

const value = document.createElement('output');
count.subscribe(v => value.value = v);

const increment = document.createElement('button');
increment.onclick = () => count.value++;
increment.textContent = '+';

document.body.append(decrement, value, increment);
});

const { signal, effect } = preactSignalsCore;

interface CustomStorage {
  getItem(key: string): void;
  setItem(key: string, value: string | null): void;
}

/**
 * A version of signal() that persists and recalls its value in localStorage.
 * @example
 *   const db = persistedSignal({}, 'db');
 *   db.value = {...db.value, newKey: 1});  // saves to localStorage.db
 *
 *   // in a new page/tab/JS context:
 *   const db = persistedSignal({}, 'db');
 *   db.value.newKey; // 1  (loaded from localStoage.db)
 */
function persistedSignal<T>(
  initialValue: T,
  key: string,
  storage: Storage | CustomStorage = localStorage
) {
  const sig = signal(initialValue);
  let skipSave = true;

  // try to hydrate state from storage:
  function load() {
    skipSave = true;
    try {
      const stored = JSON.parse(storage.getItem(key));
      if (stored != null) sig.value = stored;
    } catch (err) {
      // ignore blocked storage access
    }
    skipSave = false;
  }

  effect(() => {
    const value = sig.value;
    if (skipSave) return;
    try {
      storage.setItem(key, JSON.stringify(value));
    } catch (err) {
      // ignore blocked storage access
    }
  });

  // if another tab changes the launch tracking state, update our in-memory copy:
  if (typeof addEventListener === "function") {
    addEventListener("storage", (ev) => {
      if (ev.key === key) load();
    });
  }

  load();
  return sig;
}