UNPKG

react-unprop

Version:

A lightweight React signal system for sharing reactive state across components — no prop drilling, no context, no global state libraries. Just pure reactivity with persistent storage.

69 lines (68 loc) 2.24 kB
// src/index.ts import { useSyncExternalStore } from "react"; import CryptoJS from "crypto-js"; var subscribers = /* @__PURE__ */ new Map(); var DEFAULT_SECRET = "__signal_package_secret__"; var isBrowser = typeof window !== "undefined"; var encrypt = (data, secret) => CryptoJS.AES.encrypt(JSON.stringify(data), secret).toString(); var decrypt = (cipher, secret) => JSON.parse(CryptoJS.AES.decrypt(cipher, secret).toString(CryptoJS.enc.Utf8)); function createSignal(initialValue, options = {}) { let value = initialValue; const key = Symbol("signal"); subscribers.set(key, /* @__PURE__ */ new Set()); const { persistKey, encrypted = false, secret = DEFAULT_SECRET } = options; const load = (raw) => { if (!raw) return; try { value = encrypted ? decrypt(raw, secret) : JSON.parse(raw); } catch (e) { console.warn("[createSignal] Failed to parse persisted value:", e); } }; if (isBrowser && persistKey) { try { load(localStorage.getItem(persistKey)); window.addEventListener("storage", (e) => { if (e.key === persistKey && e.newValue !== e.oldValue) { load(e.newValue); subscribers.get(key)?.forEach((cb) => cb()); } }); } catch (e) { console.warn("[createSignal] Browser storage unavailable:", e); } } const get = () => value; const set = (next) => { const newValue = typeof next === "function" ? next(value) : next; if (newValue !== value) { value = newValue; if (isBrowser && persistKey) { try { const toStore = encrypted ? encrypt(value, secret) : JSON.stringify(value); localStorage.setItem(persistKey, toStore); } catch (e) { console.warn("[createSignal] Persist failed:", e); } } subscribers.get(key)?.forEach((cb) => cb()); } }; const subscribe = (cb) => { subscribers.get(key)?.add(cb); return () => subscribers.get(key)?.delete(cb); }; return { get, set, subscribe }; } function useSignal(signal) { return useSyncExternalStore( signal.subscribe, signal.get, // Server snapshot - same as client snapshot for consistent hydration signal.get ); } export { createSignal, useSignal };