https://github.com/stackoverprof/use-shared-state
[NPM] Sharing react state made simple
https://github.com/stackoverprof/use-shared-state
Last synced: 4 days ago
JSON representation
[NPM] Sharing react state made simple
- Host: GitHub
- URL: https://github.com/stackoverprof/use-shared-state
- Owner: stackoverprof
- License: mit
- Created: 2025-08-18T02:31:07.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2026-02-03T03:54:07.000Z (5 months ago)
- Last Synced: 2026-02-03T17:40:20.646Z (5 months ago)
- Language: TypeScript
- Homepage:
- Size: 118 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# use-shared-state
[](https://badge.fury.io/js/%40stackoverprof%2Fuse-shared-state)
[](https://opensource.org/licenses/MIT)
[](http://www.typescriptlang.org/)
A lightweight React hook for sharing state across components with optional localStorage persistence and cross-tab synchronization.
## ๐ Live Demo
**[Try it live โ](https://use-shared-state-demo.vercel.app/)**
See real-time state sharing, persistence, and cross-tab synchronization in action!
## Features
- ๐ **Simple API** - Drop-in replacement for `useState` with cross-component sharing
- ๐พ **Optional Persistence** - Use `@` prefix for localStorage persistence
- ๐ **Cross-tab Sync** - Automatic synchronization across browser tabs
- โก **High Performance** - Optimized with minimal overhead using Map storage
- ๐ก๏ธ **Type Safe** - Full TypeScript support with generics
- ๐ฏ **Lite SWR** - Built with custom lightweight SWR implementation (~100 lines)
- ๐งช **Zero Dependencies** - No external dependencies except React
## Installation
```bash
# Install the library
npm install @stackoverprof/use-shared-state
```
> **Note:** React >=16.8.0 is required (peer dependency)
## Quick Start
```tsx
import useSharedState from "@stackoverprof/use-shared-state";
// Basic shared state (memory only)
const [count, setCount] = useSharedState("counter", 0);
// Persistent shared state (localStorage + cross-tab sync)
const [user, setUser] = useSharedState("@user", { name: "John" });
// โณ Saved in localStorage as "shared@user"
```
## API Reference
### `useSharedState(key: string, initialValue?: T)`
Returns a tuple `[state, setState]` similar to React's `useState`.
#### Parameters
- `key` - Unique identifier for the shared state
- Regular keys: Memory-only storage
- Keys with `@` prefix: Persistent localStorage + cross-tab sync
- `initialValue` - Default value when state is undefined
#### Returns
- `state` - Current state value (T | undefined)
- `setState` - Function to update state, supports value or updater function
## Performance
- **Memory-only keys**: ~0.1ms overhead
- **Persistent keys**: ~2-3ms overhead (includes localStorage operations)
- **Cross-tab sync**: Automatic with StorageEvent API
- **Memory usage**: Efficient Map-based storage with automatic cleanup
- **Re-renders**: Only components using the changed state key re-render
## Re-rendering Behavior
**Important:** Only components that actively use a shared state key will re-render when that state changes.
โ
**Precise targeting**: Only components using the changed key re-render
โ
**Parent isolation**: Parent won't re-render unless it uses shared state
โ
**Sibling isolation**: Unrelated siblings won't re-render
โ
**Performance**: Better than Context (which can cause cascade re-renders)
## Comparison with Alternatives
| Feature | use-shared-state | Redux | Context | localStorage |
| -------------------- | ---------------- | ------ | ------- | ------------ |
| Setup complexity | Minimal | High | Medium | Manual |
| TypeScript support | Full | Good | Good | Manual |
| Cross-component sync | โ
| โ
| โ
| โ |
| Persistence | Optional | Manual | โ | Manual |
| Cross-tab sync | โ
| Manual | โ | Manual |
| Performance | High | Medium | Low\* | High |
| Bundle size | Small | Large | None | None |
\*Context can cause unnecessary re-renders
## Best Practices
1. **Use regular keys for temporary state**
```tsx
const [loading, setLoading] = useSharedState("loading", false);
```
2. **Use @ prefix for data that should persist**
```tsx
const [settings, setSettings] = useSharedState("@user-settings", {});
```
3. **Provide default values for better TypeScript inference**
```tsx
const [items, setItems] = useSharedState("items", []);
```
4. **Use updater functions for complex state changes**
```tsx
setCart((prev) => ({ ...prev, total: calculateTotal(prev.items) }));
```
## Cleanup & Memory Management
### **Automatic Cleanup**
- โ
**Lite SWR reference counting** - Cleans up when ALL components using a key unmount
- โ
**Event listeners removed** - Cross-tab sync listeners auto-cleanup
- โ
**Memory efficient** - Map-based storage with garbage collection
### **What Gets Cleaned Up**
| Type | Lite SWR Cleanup | localStorage Cleanup |
| -------------- | --------------------- | ---------------------------- |
| `"user-data"` | โ
Auto (memory only) | โ N/A |
| `"@user-data"` | โ
Memory cache only | โ Stays until manual delete |
### **Manual Cleanup**
```tsx
import { sharedStateUtils } from "@stackoverprof/use-shared-state";
// Clear specific keys
sharedStateUtils.delete("temp-data"); // Memory only
sharedStateUtils.delete("@user-session"); // Memory + localStorage
// Clear all (with/without persistent)
sharedStateUtils.clear(false); // Memory only
sharedStateUtils.clear(true); // Memory + localStorage
// Route cleanup
useEffect(
() => () => {
sharedStateUtils.delete("dashboard-filters");
},
[]
);
```
## Utility Functions
The library provides debugging utilities via `sharedStateUtils`:
```tsx
import { sharedStateUtils } from "@stackoverprof/use-shared-state";
// Get all current keys
console.log(sharedStateUtils.getKeys());
// Get current state size
console.log(sharedStateUtils.getSize());
// Clear all state (optionally including persistent)
sharedStateUtils.clear(true);
// Delete specific key
sharedStateUtils.delete("some-key");
// Get all persistent keys
console.log(sharedStateUtils.getPersistentKeys());
```
## Requirements
- React >= 16.8.0
## Examples
### Basic Counter
```tsx
import useSharedState from "@stackoverprof/use-shared-state";
function Counter() {
const [count, setCount] = useSharedState("counter", 0);
return (
Count: {count}
setCount(count + 1)}>Increment
);
}
```
### Shopping Cart with Persistence
```tsx
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
function ProductList() {
const [cartItems, setCartItems] = useSharedState(
"@cart-items",
[]
);
const addToCart = (product: CartItem) => {
setCartItems((prev) => {
const existing = prev?.find((item) => item.id === product.id);
if (existing) {
return (
prev?.map((item) =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
) || []
);
}
return [...(prev || []), { ...product, quantity: 1 }];
});
};
return
{/* Product list */};
}
function CartSummary() {
const [cartItems] = useSharedState("@cart-items", []);
const total =
cartItems?.reduce((sum, item) => sum + item.price * item.quantity, 0) ||
0;
return (
Cart ({cartItems?.length || 0} items)
Total: ${total.toFixed(2)}
);
}
```
### Cross-Component Form State
```tsx
interface FormData {
name: string;
email: string;
preferences: string[];
}
function Step1() {
const [formData, setFormData] = useSharedState("@form-data", {
name: "",
email: "",
preferences: [],
});
return (
setFormData((prev) => ({
...prev!,
name: e.target.value,
}))
}
placeholder="Name"
/>
);
}
function Step2() {
const [formData, setFormData] = useSharedState("@form-data");
return (
Hello, {formData?.name}!
setFormData((prev) => ({
...prev!,
email: e.target.value,
}))
}
placeholder="Email"
/>
);
}
```
## License
MIT
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.