@fibersse/react
April 3, 2026 · View on GitHub
React hooks for fibersse — replace polling with SSE cache invalidation in one line.
Install
npm install @fibersse/react
Peer dependency: React 18+
Quick Start
import { useSSEInvalidation } from '@fibersse/react';
import { useQueryClient } from '@tanstack/react-query';
function App() {
const queryClient = useQueryClient();
const { connected } = useSSEInvalidation({
topics: ['orders', 'products', 'dashboard'],
onInvalidate: (resource, action, id) => {
queryClient.invalidateQueries({ queryKey: [resource] });
if (id) queryClient.invalidateQueries({ queryKey: [resource, id] });
},
onProgress: (id, current, total, pct) => {
console.log(`Import ${id}: ${pct}%`);
},
onComplete: (id, status) => {
if (status === 'completed') toast.success('Done!');
},
});
return <span>{connected ? 'Live' : 'Connecting...'}</span>;
}
That's it. No setInterval. No polling. Your queries refetch only when data actually changes.
Hooks
useSSEInvalidation — High-Level (Recommended)
The primary hook for replacing polling. Connects to a fibersse hub and routes events to your cache layer.
const { connected, connectionId, disconnect, reconnect } = useSSEInvalidation({
// Required
topics: ['orders', 'dashboard'],
// Cache invalidation (the main thing)
onInvalidate: (resource, action, resourceId, hint) => {
queryClient.invalidateQueries({ queryKey: [resource] });
},
// Batch invalidation (multiple resources in one event)
onBatch: (events) => {
events.forEach(e => queryClient.invalidateQueries({ queryKey: [e.resource] }));
},
// Progress tracking (coalesced — 1000 updates → ~15 events)
onProgress: (resourceId, current, total, pct, hint) => {
setProgress(pct);
},
// Completion signals
onComplete: (resourceId, status, hint) => {
if (status === 'completed') refetchEverything();
},
// Generic refresh signals
onSignal: () => {
queryClient.invalidateQueries();
},
// Optional config
url: '/api/v1/events/stream', // SSE endpoint (default)
ticketUrl: '/api/sse/ticket', // Ticket endpoint (default)
enabled: isAuthenticated, // Connect only when ready
visibilityAware: true, // Disconnect on hidden tab (default)
maxReconnectAttempts: 8, // Exponential backoff
});
useSSE — Low-Level
Full control over event handling. Use when you need custom event types beyond invalidation.
const { connected } = useSSE({
topics: ['notifications', 'live'],
onEvent: {
notification: (data) => showToast(data),
live_visitors: (data) => setVisitors(data),
intelligence: (data) => setSnapshot(data),
},
onConnect: ({ connection_id, topics }) => {
console.log('Connected:', connection_id);
},
onServerShutdown: () => {
console.log('Server draining, will reconnect...');
},
});
With SWR
import { useSWRConfig } from 'swr';
const { mutate } = useSWRConfig();
useSSEInvalidation({
topics: ['orders'],
onInvalidate: (resource) => mutate(`/api/${resource}`),
});
Features
- Ticket authentication — POST for a one-time ticket, then connect with it (EventSource can't send headers)
- Exponential backoff — 3s → 6s → 12s → ... → 60s cap, with jitter
- Visibility-aware — disconnects when tab is hidden, reconnects when visible
- Type-safe — full TypeScript types for all event payloads
- Zero runtime dependencies — only React as a peer dependency
- Framework agnostic — works with TanStack Query, SWR, Zustand, Redux, or plain setState
Auth Flow
1. Client calls POST /api/sse/ticket (with JWT cookie/header)
2. Server returns { ticket: "one-time-token", topics: [...] }
3. Client opens EventSource at /events?ticket=TOKEN&topics=orders,dashboard
4. On disconnect/error → exponential backoff → get new ticket → reconnect
License
MIT — Vinod Morya