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:

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.

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
})

Interaction with Other System Parts


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.