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;
}