A lightweight, reactive state management library for React that makes state synchronization effortless.
- 🪶 Lightweight: Minimal bundle size with zero dependencies
- ⚡ Reactive: Automatic component updates when state changes
- 🔄 Sync Anywhere: URL parameters, localStorage, sessionStorage, or across browser tabs
- 🎯 Type-Safe: Full TypeScript support with excellent type inference
- 🧩 Composable: Mix and match different sync strategies
- 🚀 Simple API: Intuitive hooks-based interface
→ Interactive Examples on StackBlitz
npm install @zignal/core
import { createZignal } from '@zignal/core';
// Create a reactive counter
const useCounter = createZignal(0);
function Counter() {
const [count, setCount] = useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
}
- @zignal/core - The main reactive state management library
- @zignal/persist - Sync state with localStorage/sessionStorage
- @zignal/paramagic - Sync state with URL parameters
- @zignal/sync - Sync state across browser tabs
import { createZignal } from '@zignal/core';
const useCounter = createZignal(0);
function SimpleCounter() {
const [count, setCount] = useCounter();
return (
<div>
<h3>Count: {count}</h3>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
import { createZignal } from '@zignal/core';
import { write } from '@zignal/persist';
const useCounter = write(createZignal(0), { key: 'my-counter' });
function PersistentCounter() {
const [count, setCount] = useCounter();
// State automatically persists to localStorage
return <div>Count: {count}</div>;
}
import { createZignal } from '@zignal/core';
import { buildQueryString } from '@zignal/paramagic';
const useUrlCounter = buildQueryString(
createZignal({ count: 0 }),
{ key: 'count' }
);
function UrlCounter() {
const [state, setState] = useUrlCounter();
// State syncs with URL parameters automatically
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => setState({ count: state.count + 1 })}>
Increment
</button>
</div>
);
}
import { createZignal } from '@zignal/core';
import { broadcast } from '@zignal/sync';
const useSharedCounter = broadcast(createZignal(0), { key: 'shared-counter' });
function SharedCounter() {
const [count, setCount] = useSharedCounter();
// Changes sync across all open tabs automatically
return <div>Shared Count: {count}</div>;
}
Zignal follows a simple but powerful pattern:
- Create a zignal with
createZignal(initialValue)
- Enhance it with sync strategies (persist, URL, broadcast)
- Use it in components like any React hook
// 1. Create
const useCounter = createZignal(0);
// 2. Enhance (optional)
const usePersistentCounter = write(useCounter, { key: 'counter' });
const useUrlCounter = buildQueryString(useCounter, { key: 'count' });
const useSharedCounter = broadcast(useCounter, { key: 'shared' });
// 3. Use
function MyComponent() {
const [count, setCount] = useCounter(); // or any enhanced version
return <div>{count}</div>;
}
import { createZignal } from '@zignal/core';
import { write } from '@zignal/persist';
import { buildQueryString } from '@zignal/paramagic';
// Combine URL sync + localStorage persistence
const useAdvancedCounter = write(
buildQueryString(createZignal({ count: 0 }), { key: 'count' }),
{ key: 'advanced-counter' }
);
import { buildQueryString } from '@zignal/paramagic';
const useCustomSync = buildQueryString(createZignal(new Date()), {
key: 'date',
serialize: (date) => date.toISOString(),
deserialize: (str) => new Date(str)
});
We welcome contributions! Please see our Contributing Guide for details.
MIT © Zignal Team