Subscription for Real-Time Data
This module provides middleware and hooks to enable React components to subscribe to external real-time data sources seamlessly within the SWR (stale-while-revalidate) ecosystem. It manages the lifecycle of subscriptions, ensures efficient resource usage with reference counting, and integrates subscription updates into SWR's cache and revalidation flow.
Core Concepts and Purpose
Why Subscription Support Exists
Traditional SWR hooks focus on fetching data on demand and revalidating it based on triggers like focus, reconnect, or intervals. However, many modern applications require continuous, real-time updates from external sources such as WebSocket streams, event emitters, or other push-based data providers.
The Subscription for Real-Time Data module addresses this need by:
Allowing components to subscribe to live data streams.
Automatically managing subscription lifecycles to prevent resource leaks.
Integrating subscription updates directly into the SWR cache and state.
Supporting multiple subscribers to the same data source with efficient sharing.
This approach combines the declarative data fetching model of SWR with real-time reactive updates, improving UI responsiveness without complicating component logic.
How the Module Works
Subscription Middleware
The core of the module is the subscription middleware, implemented in src/subscription/index.ts. It is a higher-order SWR middleware function that extends a base SWR hook (usually the core useSWR hook) to support subscriptions.
Key Serialization and Namespacing:
The middleware serializes the subscription key and prefixes it with a special string ($sub$) to avoid conflicts with other keys in the global cache.Subscription Reference Counting:
AWeakMapnamedsubscriptionStoragekeeps track of active subscriptions per cache boundary. For each subscription key, it stores:A count of active subscribers.
A disposer function to unsubscribe from the external data source.
Lifecycle Management with Effects:
UsinguseIsomorphicLayoutEffect, the middleware:Increments the subscription count when a component mounts using the subscription.
Calls the provided
subscribefunction (passed by the user) exactly once when the first subscriber appears.Stores the returned unsubscribe function.
Decrements the count when a component unmounts.
Calls the unsubscribe function when the last subscriber for that key unmounts.
Data and Error Propagation:
The middleware creates anextcallback, passed to the user'ssubscribefunction, which receives new data or errors from the external source. This callback updates the SWR cache with data or error accordingly, triggering re-renders in subscribed components.Return Value:
The middleware returns an object with getters fordataanderrorfrom the underlying SWR hook, enabling React components to access the subscription state reactively.
The useSWRSubscription Hook
Wrapped with withMiddleware, useSWRSubscription integrates the subscription middleware with the core SWR hook, exposing a user-friendly API:
const { data, error } = useSWRSubscription(key, (key, { next }) => {
const unsubscribe = dataSource.subscribe(key, (err, data) => {
next(err, data)
})
return unsubscribe
})
Parameters:
key: Identifier for the subscription.subscribe: Function that sets up the external subscription and returns an unsubscribe callback.config: Optional SWR configuration.
Functionality:
Internally manages reference counting and cache updates.
Ensures multiple components subscribing to the same key share the subscription.
Provides reactive
dataanderrorto components.
Interaction with Other System Parts
The subscription middleware composes on top of the core SWR hook (
useSWR), leveraging its cache and mutation mechanisms.It uses internal utilities like
serializefor key management andcreateCacheHelperfor cache interaction.The middleware integrates with the global cache provider to isolate subscription states per cache boundary, allowing multiple independent SWR zones.
Components using
useSWRSubscriptioninteract only with the hook API, abstracting away subscription lifecycle and cache management.The subscription module is designed as an optional extension that fits into the SWR middleware architecture, enabling flexible feature composition.
Important Concepts and Design Patterns
Reference Counting for Resource Efficiency
The middleware counts how many components are subscribed to the same key and shares a single subscription to the external source. This pattern prevents redundant subscriptions, saving resources and avoiding duplicated data processing.
Cache Boundary Isolation
By storing subscription states in a WeakMap keyed by the cache object, the module scopes subscriptions to distinct SWR cache boundaries. This design supports complex applications with multiple SWR contexts without cross-interference.
Declarative Subscription API
The subscribe function passed by the user receives a next callback to report new data or errors, maintaining a clean separation between subscription setup and SWR cache mutation. This pattern supports various external event sources.
Integration with React Lifecycle
Using useIsomorphicLayoutEffect ensures subscription setup and teardown occur at appropriate times within React's commit phase, handling both client and server environments safely.
Code Reference Highlights
Subscription Storage Initialization and Reference Counting
if (!subscriptionStorage.has(cache)) {
subscriptionStorage.set(cache, [
new Map<string, number>(),
new Map<string, () => void>()
])
}
const [subscriptions, disposers] = subscriptionStorage.get(cache)!
Setting Up Subscription and Unsubscription
useIsomorphicLayoutEffect(() => {
if (!subscriptionKey) return
const refCount = subscriptions.get(subscriptionKey) || 0
subscriptions.set(subscriptionKey, refCount + 1)
if (!refCount) {
const dispose = subscribe(args, { next })
disposers.set(subscriptionKey, dispose)
}
return () => {
const count = subscriptions.get(subscriptionKey)! - 1
subscriptions.set(subscriptionKey, count)
if (!count) {
const dispose = disposers.get(subscriptionKey)
dispose?.()
}
}
}, [subscriptionKey])
Handling Incoming Data and Errors
const next: SWRSubscriptionOptions<Data, Error>['next'] = (error, data) => {
if (error !== null && typeof error !== 'undefined') {
set({ error })
} else {
set({ error: undefined })
swr.mutate(data, false)
}
}
Mermaid Diagram: Subscription Lifecycle Flow
sequenceDiagram
participant Component as React Component
participant Hook as useSWRSubscription Hook
participant Middleware as Subscription Middleware
participant Cache as SWR Cache
participant DataSource as External Data Source
Component->>Hook: Calls useSWRSubscription(key, subscribe)
Hook->>Middleware: Initializes subscription with key
Middleware->>Cache: Checks subscription count
alt No existing subscription
Middleware->>DataSource: Calls subscribe(args, next)
DataSource-->>Middleware: Returns unsubscribe function
Middleware->>Cache: Stores disposer & sets count=1
else Existing subscription
Middleware->>Cache: Increments count
end
DataSource-->>Middleware: Emits (error, data) events
Middleware->>Cache: Updates cache with data or error
Cache-->>Hook: Triggers re-render with new data/error
Hook-->>Component: Provides data and error states
Component-->>Hook: Unmounts
Hook->>Middleware: Decrements subscription count
alt Last subscriber
Middleware->>DataSource: Calls unsubscribe
Middleware->>Cache: Removes disposer
end
This comprehensive subscription module enables efficient, real-time data integration within the existing SWR framework, enhancing React applications with live updates while maintaining performance, scalability, and code clarity.