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.

320 lines (231 loc) 8.31 kB
# react-unprop A tiny, open-source React signal system for reactive state sharing — no props, no context. Fast, flexible, and works across your entire React tree. [![npm version](https://badge.fury.io/js/react-unprop.svg)](https://badge.fury.io/js/react-unprop) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) ## ✨ Features - ⚡ **Fast reactive updates** — re-renders only when signals change - 🧼 **No props. No context.** Just unprops - 🎯 **Auto re-render** on signal update - 🔁 **Share state** between siblings, parents, or anywhere - 🔄 **Cross-tab synchronization** — keep state in sync across browser tabs - 🖥️ **SSR compatible** — works with Next.js and other SSR frameworks - ✅ **Works with primitives, arrays, and objects** - 🔐 **Optional encrypted persistence via localStorage** - 🧩 **Encryption built-in** — no config needed - 🧠 **Environment-agnostic** — works with CRA, Vite, Next.js, etc. - 💙 **Supports both JavaScript and TypeScript** - 📦 **Tiny bundle size** — less than 20KB gzipped - 🚀 **Zero dependencies** - 🌍 **Open Source** — built by the community, for the community ## 📦 Installation Install using npm: ```bash npm install react-unprop ``` or using yarn: ```bash yarn add react-unprop ``` or using pnpm: ```bash pnpm add react-unprop ``` ## 🚀 Quick Start Create and use react-unprops just like global `useState` — but better. ```jsx import { createSignal, useSignal } from "react-unprop"; const counter = createSignal(0); function Counter() { const count = useSignal(counter); return <h1>{count}</h1>; } function IncrementButton() { return <button onClick={() => counter.set((c) => c + 1)}>Increment</button>; } ``` ✅ Both components update automatically when the signal changes. ## 🔐 Persisting Encrypted State (Optional) `react-unprop` allows optional persistence to localStorage with built-in AES encryption. ### Example ```js // Persistent but not encrypted const userSignal = createSignal({ name: "Alice" }, { persistKey: "user" }); // Persistent and encrypted const sensitiveDataSignal = createSignal( { apiKey: "secret123" }, { persistKey: "sensitive", encrypted: true } ); ``` The state will be securely stored in localStorage and automatically decrypted on load. ## 🔄 Cross-Tab Synchronization When using persistent signals, `react-unprop` automatically synchronizes your state across browser tabs. Any changes made in one tab are instantly reflected in others with no additional configuration. ### Example ```js // In any tab const userSignal = createSignal({ name: "Alice" }, { persistKey: "user" }); // In one tab, update the signal userSignal.set({ name: "Bob" }); // In another tab, the signal will automatically update // and components using useSignal(userSignal) will re-render ``` This works for both encrypted and unencrypted signals. ## 🖥️ Server-Side Rendering Support `react-unprop` is fully compatible with Next.js and other SSR frameworks. The library automatically detects server environments and handles them appropriately. ### Usage with Next.js ```jsx // shared/signals.js import { createSignal } from "react-unprop"; // Works in both client and server environments export const userSignal = createSignal({ name: "Guest" }); // pages/index.js import { useSignal } from "react-unprop"; import { userSignal } from "../shared/signals"; export default function Home() { // Works with SSR and hydration const user = useSignal(userSignal); return <div>Hello, {user.name}!</div>; } ``` Storage-based features (persistence, cross-tab sync) automatically work only in the browser, with no special configuration needed. ## 🧠 API Reference ### `createSignal(initialValue, options?)` Creates a reactive signal. **Parameters:** - `initialValue: T` — The initial value of the signal - `options.persistKey?: string` — Key for localStorage persistence and cross-tab sync - `options.encrypted?: boolean` — Whether to encrypt persisted data (default: false) - `options.secret?: string` — Custom encryption secret (optional) **Returns an object with:** - `get(): T` — returns current value - `set(newValue | updaterFn): void` — sets value or updates with function - `subscribe(callback): () => void` — used internally by useSignal ### `useSignal(signal)` React hook that subscribes to a signal and auto-rerenders on update. **Parameters:** - `signal: Signal<T>` — The signal to subscribe to **Returns:** The current value of the signal. ## 🧪 Code Examples ### 🔢 Counter ```jsx const counter = createSignal(0); function App() { const count = useSignal(counter); return ( <div> <h1>{count}</h1> <button onClick={() => counter.set((c) => c + 1)}>+</button> <button onClick={() => counter.set((c) => c - 1)}>-</button> </div> ); } ``` ### 👥 Shared User State ```jsx const userSignal = createSignal({ name: "John", age: 25 }, { persist: true }); function Profile() { const user = useSignal(userSignal); return ( <div> {user.name}, {user.age} </div> ); } ``` ### 📋 Dynamic List (Array) ```jsx const itemsSignal = createSignal([]); function AddItem() { return ( <button onClick={() => itemsSignal.set((prev) => [...prev, `Item ${prev.length + 1}`]) } > Add Item </button> ); } function ItemList() { const items = useSignal(itemsSignal); return ( <ul> {items.map((item, idx) => ( <li key={idx}>{item}</li> ))} </ul> ); } ``` ### 🔄 Toggle State (Boolean) ```jsx const darkModeSignal = createSignal(false); function ToggleDarkMode() { const darkMode = useSignal(darkModeSignal); return ( <button onClick={() => darkModeSignal.set(!darkMode)}> {darkMode ? "Disable" : "Enable"} Dark Mode </button> ); } ``` ### 📝 Form Input Binding ```jsx const formSignal = createSignal({ name: "", email: "" }); function Form() { const form = useSignal(formSignal); return ( <form> <input value={form.name} onChange={(e) => formSignal.set((f) => ({ ...f, name: e.target.value })) } placeholder="Name" /> <input value={form.email} onChange={(e) => formSignal.set((f) => ({ ...f, email: e.target.value })) } placeholder="Email" /> </form> ); } ``` ## 🔄 Migration Guide ### From useState ```jsx // Before const [count, setCount] = useState(0); // After const countSignal = createSignal(0); const count = useSignal(countSignal); ``` ### From Context ```jsx // Before: Context const ThemeContext = createContext(); // After: Signal const themeSignal = createSignal("light"); ``` ## 🌟 Why Open Source? - 👐 **Built with collaboration in mind** - 🛠️ **Customizable and extensible** - 🌱 **Actively maintained and open to PRs** - 📣 **Community-driven roadmap** - 🧩 **Transparent and secure codebase** ## 🤝 Contributing We welcome all contributions! Whether it's improving docs, fixing bugs, or adding features — you're welcome to join the journey. 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/AmazingFeature`) 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 4. Push to the branch (`git push origin feature/AmazingFeature`) 5. Open a Pull Request ## 📄 License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. --- **Made with ❤️ by Ayush Chauhan and the open-source community** [Report Bug](https://github.com/aayushchauhan10/react-unprop/issues) · [Request Feature](https://github.com/aayushchauhan10/react-unprop/issues) · [Documentation](https://github.com/aayushchauhan10/react-unprop#readme)