An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# use-shared-state

[![npm version](https://badge.fury.io/js/%40stackoverprof%2Fuse-shared-state.svg)](https://badge.fury.io/js/%40stackoverprof%2Fuse-shared-state)
[![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/)

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.