lit-share
v0.1.1
Published
A lightweight decorator for sharing reactive state across Lit elements
Downloads
3
Maintainers
Readme
lit-share
A lightweight decorator for sharing reactive state across Lit elements.
✨ Key Features:
- Simple API - Use
@share()decorator just like@property() - Automatic synchronization - Changes in one element instantly reflect in all others
- Type-safe - Full TypeScript support with generics
- Performance optimized - O(1) lookups with Map-based storage
- Custom change detection - Support for
hasChangedlike Lit's@property - Listener support - Monitor value changes outside component lifecycle
Installation
npm install lit-shareUsage
Basic Sharing
Share state across multiple elements with a simple decorator:
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { share } from 'lit-share';
@customElement('counter-display')
class CounterDisplay extends LitElement {
@share() count = 0;
render() {
return html`
<p>Count: ${this.count}</p>
<button @click=${() => this.count++}>Increment</button>
`;
}
}
@customElement('counter-viewer')
class CounterViewer extends LitElement {
@share() count = 0;
render() {
return html`<p>Current count: ${this.count}</p>`;
}
}Both components share the same count value. Clicking the button in counter-display automatically updates counter-viewer.
Custom Key
Share the same value with different property names:
@customElement('user-profile')
class UserProfile extends LitElement {
@share({ key: 'currentUser' }) user = null;
render() {
return html`<p>User: ${this.user?.name}</p>`;
}
}
@customElement('user-settings')
class UserSettings extends LitElement {
@share({ key: 'currentUser' }) currentUser = null;
render() {
return html`<p>Settings for: ${this.currentUser?.name}</p>`;
}
}Custom Change Detection
Use custom comparison logic like Lit's hasChanged:
interface Data {
id: number;
value: string;
}
@customElement('data-display')
class DataDisplay extends LitElement {
@share<Data | null>({
key: 'appData',
hasChanged: (newVal, oldVal) => {
// Only update when ID changes
return !oldVal || newVal?.id !== oldVal.id;
}
})
data: Data | null = null;
render() {
return html`<p>ID: ${this.data?.id}, Value: ${this.data?.value}</p>`;
}
}Common patterns:
- Deep equality:
JSON.stringify(a) !== JSON.stringify(b) - Reference equality:
a !== b(default) - Property-based:
a?.id !== b?.id - Always update:
() => true
Direct API Access
Access shared values without the decorator:
import { LitShare } from 'lit-share';
// Get with type safety
const count = LitShare.get<number>('count', 0);
// Set with type safety
LitShare.set<number>('count', 42);
// Force update (skip change detection)
LitShare.set<number>('count', 42, true);Listeners
Monitor value changes outside of element lifecycle:
import { LitShare } from 'lit-share';
// Add listener
const unsubscribe = LitShare.addListener<number>('count', (newValue, oldValue) => {
console.log(`Count changed: ${oldValue} → ${newValue}`);
});
// Remove listener
unsubscribe();
// Or remove directly
LitShare.removeListener('count', listener);Lifecycle integration:
class MyElement extends LitElement {
private unsubscribe?: () => void;
connectedCallback() {
super.connectedCallback();
this.unsubscribe = LitShare.addListener('key', this.handleChange);
}
disconnectedCallback() {
super.disconnectedCallback();
this.unsubscribe?.();
}
private handleChange = (newVal: unknown, oldVal: unknown) => {
// Handle change
}
}API Reference
@share(options?)
Decorator for creating shared properties.
@share<T>(options?: ShareDeclaration<T>)Options:
key?: string- Custom key for the shared value (defaults to property name)hasChanged?: (newValue: T, oldValue: T) => boolean- Custom change detection function
LitShare.get<T>(key, defaultValue?)
Get a shared value with type safety.
LitShare.get<T>(key: string, defaultValue?: T): T | undefinedParameters:
key: string- The shared value keydefaultValue?: T- Default value if key doesn't exist
Returns: T | undefined
LitShare.set<T>(key, value, force?)
Set a shared value and notify all registered elements.
LitShare.set<T>(key: string, value: T, force?: boolean): voidParameters:
key: string- The shared value keyvalue: T- The new valueforce?: boolean- Skip change detection and always update
LitShare.addListener<T>(key, listener)
Add a listener for value changes.
LitShare.addListener<T>(key: string, listener: (newValue: T, oldValue: T | undefined) => void): () => voidParameters:
key: string- The shared value keylistener: (newValue: T, oldValue: T | undefined) => void- Callback function
Returns: () => void - Unsubscribe function
LitShare.removeListener<T>(key, listener)
Remove a listener.
LitShare.removeListener<T>(key: string, listener: ShareListener<T>): voidParameters:
key: string- The shared value keylistener: ShareListener<T>- The listener to remove
How It Works
- Decorator Registration: When
@share()is applied to a property, it replaces the property with a getter/setter - Automatic Registration: On getter access, the element automatically registers itself for updates
- Value Synchronization: On setter call, the value is stored centrally and all registered elements are notified
- Reactive Updates: Each registered element receives
requestUpdate()calls, triggering Lit's normal rendering cycle
Performance
lit-share uses a Map-based architecture for O(1) lookups and updates:
- Element registration: O(1)
- Value updates: O(n) where n is the number of subscribed elements
- Change detection: O(1)
Common Patterns
Shared Application State
@customElement('app-root')
class AppRoot extends LitElement {
@share({ key: 'theme' }) theme = 'light';
@share({ key: 'user' }) user = null;
}
@customElement('theme-toggle')
class ThemeToggle extends LitElement {
@share({ key: 'theme' }) theme: string;
toggle() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
}
}Global Counters/Flags
@customElement('loading-spinner')
class LoadingSpinner extends LitElement {
@share() loadingCount = 0;
render() {
return this.loadingCount > 0
? html`<div class="spinner"></div>`
: html``;
}
}
// Elsewhere
function startLoading() {
LitShare.set('loadingCount', LitShare.get('loadingCount', 0) + 1);
}
function stopLoading() {
LitShare.set('loadingCount', Math.max(0, LitShare.get('loadingCount', 0) - 1));
}Best Practices
- Use TypeScript - Leverage generics for type safety
- Name keys explicitly - Use
keyoption for important shared state - Implement hasChanged for objects - Avoid unnecessary re-renders
- Clean up listeners - Always unsubscribe in
disconnectedCallback - Set initial values consistently - Avoid conflicts by using a single source
- Document shared state - Comment which values are shared and why
Examples
Check out the examples directory for more usage patterns.
License
MIT
Author
mizushino
