Examples

Prev Next

Polling Manager Examples

This page shows practical usage patterns for Polling Manager in two contexts:

  • Plain JavaScript/TypeScript (domain adapter or service-style orchestration)
  • React components (pages/modules)

Plain JavaScript Example

Use this pattern in non-React orchestration layers where you want a reusable subscription API.

import { pollingManager } from '@/lib/internal/PollingManager';

type EncodingProgress = {
  status: 'PROCESSING' | 'READY' | 'FAILED';
  percent: number;
};

async function fetchEncodingProgress(contentId: string): Promise<EncodingProgress> {
  // Example placeholder: call your transport layer here.
  return {
    status: 'PROCESSING',
    percent: 42,
  };
}

export function startEncodingProgressWatch(contentId: string, onProgress: (percent: number) => void) {
  return pollingManager.subscribe({
    key: `encoding:progress:${contentId}`,
    intervalMs: 5000,
    pollFn: () => fetchEncodingProgress(contentId),
    onResult: (result) => onProgress(result.percent),
    onError: (error) => {
      // Route to your own logging/monitoring path.
      console.error('Encoding poll failed', error);
    },
    shouldStop: (result) => result.status === 'READY' || result.status === 'FAILED',
  });
}

// Consumer:
const subscription = startEncodingProgressWatch('vod-123', (percent) => {
  console.log('Encoding progress:', percent);
});

// Later cleanup:
subscription.unsubscribe();

Notes

  • Call unsubscribe() from the owner that started the watch.
  • Use stable keys to get dedup across multiple consumers watching the same entity.
  • Keep domain terminal logic inside shouldStop.
  • For existing keys, task-defining options must match (pollFn, intervalMs, shouldStop, pauseWhenHidden) or subscribe fails fast.

React Example (Pages/Modules)

Recommended pattern without introducing a custom hook.

'use client';

import { useEffect, useRef, useState } from 'react';

import { pollingManager } from '@/lib/internal/PollingManager';

type EncodingProgress = {
  status: 'PROCESSING' | 'READY' | 'FAILED';
  percent: number;
};

async function fetchEncodingProgress(contentId: string): Promise<EncodingProgress> {
  return {
    status: 'PROCESSING',
    percent: 42,
  };
}

export function EncodingProgressTile({ contentId, enabled = true }: { contentId: string; enabled?: boolean }) {
  const [progress, setProgress] = useState<number>(0);
  const [error, setError] = useState<string | null>(null);

  const subscriptionRef = useRef<ReturnType<typeof pollingManager.subscribe<EncodingProgress>> | null>(null);

  useEffect(() => {
    if (!enabled || !contentId) return;

    subscriptionRef.current = pollingManager.subscribe<EncodingProgress>({
      key: `encoding:progress:${contentId}`,
      intervalMs: 5000,
      pollFn: () => fetchEncodingProgress(contentId),
      onResult: (result) => {
        setProgress(result.percent);
      },
      onError: () => {
        setError('Unable to refresh encoding progress');
      },
      shouldStop: (result) => result.status === 'READY' || result.status === 'FAILED',
    });

    return () => {
      subscriptionRef.current?.unsubscribe();
      subscriptionRef.current = null;
    };
  }, [contentId, enabled]);

  // Keep callbacks fresh without unsubscribe/resubscribe churn.
  useEffect(() => {
    subscriptionRef.current?.update({
      onResult: (result) => {
        setProgress(result.percent);
      },
      onError: () => {
        setError('Unable to refresh encoding progress');
      },
    });
  }, []);

  if (!enabled) return null;

  return (
    <div>
      <p>Encoding progress: {progress}%</p>
      {error ? <p>{error}</p> : null}
    </div>
  );
}

Shared-key dedup scenario

// List card and detail panel both subscribe to:
// key = "encoding:progress:vod-123"
//
// Result:
// - one task in PollingManager
// - one timer
// - two subscribers
// - no second immediate poll on the second subscriber

Anti-patterns

  • Creating raw setInterval loops inside each component for the same resource
  • Generating unique keys per component instance for the same entity
  • Unsubscribing/re-subscribing every rerender when callback update is enough
  • Mutating task-defining fields through update (not supported)