Skip to content

Instantly share code, notes, and snippets.

@developit
Created August 18, 2023 22:41
Show Gist options
  • Save developit/baa45015a607877a9a9e2697fb32ab8b to your computer and use it in GitHub Desktop.
Save developit/baa45015a607877a9a9e2697fb32ab8b to your computer and use it in GitHub Desktop.
import { signal, effect } from "@preact/signals-core";
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)
*
* @see https://codesandbox.io/s/preact-signals-persisted-cyxxsj?file=/src/index.mjs
*/
export 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;
}
@barelyhuman
Copy link

Might want to add in a micro queue to the load(), since during hydration theres no prop diffing so the loaded value is never actually applied

const mQ = Promise.prototype.then.bind(Promise.resolve());

export function persistedSignal(initialValue, key, storage = localStorage) {
 // ...remaining
  mQ(() => load());
  return sig;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment