0
# Real-time Subscriptions
1
2
The `useSWRSubscription` hook enables real-time data updates through WebSockets, Server-Sent Events, or other subscription mechanisms.
3
4
## Capabilities
5
6
### useSWRSubscription Hook
7
8
Hook for real-time data updates through subscription mechanisms.
9
10
```typescript { .api }
11
/**
12
* Hook for real-time data updates through subscription mechanisms
13
* @param key - Unique identifier for the subscription
14
* @param subscribe - Function that sets up the subscription
15
* @param config - Configuration options for the subscription
16
* @returns SWRSubscriptionResponse with current data and error state
17
*/
18
function useSWRSubscription<Data = any, Error = any>(
19
key: Key,
20
subscribe: SWRSubscription<Key, Data, Error>,
21
config?: SWRConfiguration<Data, Error>
22
): SWRSubscriptionResponse<Data, Error>;
23
```
24
25
**Usage Examples:**
26
27
```typescript
28
import useSWRSubscription from "swr/subscription";
29
30
// WebSocket subscription
31
const { data, error } = useSWRSubscription(
32
"ws://localhost:3001",
33
(key, { next }) => {
34
const socket = new WebSocket(key);
35
36
socket.addEventListener("message", (event) => {
37
next(null, JSON.parse(event.data));
38
});
39
40
socket.addEventListener("error", (event) => {
41
next(event);
42
});
43
44
// Return cleanup function
45
return () => socket.close();
46
}
47
);
48
49
// Server-Sent Events
50
const { data } = useSWRSubscription(
51
"/api/events",
52
(key, { next }) => {
53
const eventSource = new EventSource(key);
54
55
eventSource.onmessage = (event) => {
56
next(null, JSON.parse(event.data));
57
};
58
59
eventSource.onerror = (error) => {
60
next(error);
61
};
62
63
return () => eventSource.close();
64
}
65
);
66
67
// Firebase Realtime Database
68
const { data } = useSWRSubscription(
69
["firebase", userId],
70
([, userId], { next }) => {
71
const ref = firebase.database().ref(`users/${userId}`);
72
73
const listener = ref.on("value", (snapshot) => {
74
next(null, snapshot.val());
75
}, (error) => {
76
next(error);
77
});
78
79
return () => ref.off("value", listener);
80
}
81
);
82
```
83
84
### SWR Subscription Response
85
86
The return value from `useSWRSubscription` with real-time data and error state.
87
88
```typescript { .api }
89
interface SWRSubscriptionResponse<Data, Error> {
90
/** The current data from the subscription (undefined if no data received) */
91
data?: Data;
92
/** Error from the subscription (undefined if no error) */
93
error?: Error;
94
}
95
```
96
97
### Subscription Function
98
99
Function that sets up and manages the subscription lifecycle.
100
101
```typescript { .api }
102
type SWRSubscription<SWRSubKey, Data, Error> = (
103
key: SWRSubKey,
104
options: SWRSubscriptionOptions<Data, Error>
105
) => (() => void) | void;
106
107
interface SWRSubscriptionOptions<Data, Error> {
108
/** Function to call with new data or errors */
109
next: (error?: Error | null, data?: Data | MutatorCallback<Data>) => void;
110
}
111
112
type MutatorCallback<Data> = (currentData: Data | undefined) => Data | undefined;
113
```
114
115
**Subscription Function Examples:**
116
117
```typescript
118
// WebSocket with reconnection
119
const webSocketSubscription = (url: string, { next }: SWRSubscriptionOptions<any, Error>) => {
120
let socket: WebSocket;
121
let reconnectTimer: NodeJS.Timeout;
122
123
const connect = () => {
124
socket = new WebSocket(url);
125
126
socket.onopen = () => {
127
console.log("WebSocket connected");
128
};
129
130
socket.onmessage = (event) => {
131
next(null, JSON.parse(event.data));
132
};
133
134
socket.onclose = () => {
135
console.log("WebSocket disconnected, reconnecting...");
136
reconnectTimer = setTimeout(connect, 3000);
137
};
138
139
socket.onerror = (error) => {
140
next(new Error("WebSocket error"));
141
};
142
};
143
144
connect();
145
146
// Cleanup function
147
return () => {
148
clearTimeout(reconnectTimer);
149
socket?.close();
150
};
151
};
152
153
// Socket.io subscription
154
const socketIOSubscription = (event: string, { next }: SWRSubscriptionOptions<any, Error>) => {
155
const socket = io();
156
157
socket.on(event, (data) => {
158
next(null, data);
159
});
160
161
socket.on("error", (error) => {
162
next(error);
163
});
164
165
return () => {
166
socket.off(event);
167
socket.disconnect();
168
};
169
};
170
171
// GraphQL subscription (with graphql-ws)
172
const graphqlSubscription = (query: string, { next }: SWRSubscriptionOptions<any, Error>) => {
173
const client = createClient({
174
url: "ws://localhost:4000/graphql",
175
});
176
177
const unsubscribe = client.subscribe(
178
{ query },
179
{
180
next: (data) => next(null, data),
181
error: (error) => next(error),
182
complete: () => console.log("Subscription completed"),
183
}
184
);
185
186
return unsubscribe;
187
};
188
189
// Polling subscription (alternative to refreshInterval)
190
const pollingSubscription = (url: string, { next }: SWRSubscriptionOptions<any, Error>) => {
191
const poll = async () => {
192
try {
193
const response = await fetch(url);
194
const data = await response.json();
195
next(null, data);
196
} catch (error) {
197
next(error as Error);
198
}
199
};
200
201
// Initial poll
202
poll();
203
204
// Set up interval
205
const intervalId = setInterval(poll, 5000);
206
207
return () => clearInterval(intervalId);
208
};
209
210
// EventSource with custom headers
211
const eventSourceSubscription = (url: string, { next }: SWRSubscriptionOptions<any, Error>) => {
212
const eventSource = new EventSource(url);
213
214
eventSource.addEventListener("data", (event) => {
215
next(null, JSON.parse(event.data));
216
});
217
218
eventSource.addEventListener("error", (error) => {
219
next(new Error("EventSource error"));
220
});
221
222
// Handle specific event types
223
eventSource.addEventListener("user-update", (event) => {
224
next(null, { type: "user-update", data: JSON.parse(event.data) });
225
});
226
227
return () => eventSource.close();
228
};
229
```
230
231
### Advanced Subscription Patterns
232
233
Common patterns for complex real-time scenarios.
234
235
**Chat Application:**
236
237
```typescript
238
interface ChatMessage {
239
id: string;
240
user: string;
241
message: string;
242
timestamp: number;
243
}
244
245
function ChatRoom({ roomId }: { roomId: string }) {
246
const { data: messages, error } = useSWRSubscription(
247
roomId ? `ws://localhost:3001/chat/${roomId}` : null,
248
(key, { next }) => {
249
const socket = new WebSocket(key);
250
251
socket.onmessage = (event) => {
252
const message: ChatMessage = JSON.parse(event.data);
253
254
// Append new message to existing messages
255
next(null, (currentMessages: ChatMessage[] = []) => [
256
...currentMessages,
257
message
258
]);
259
};
260
261
socket.onerror = (error) => {
262
next(new Error("Failed to connect to chat"));
263
};
264
265
return () => socket.close();
266
}
267
);
268
269
const sendMessage = (message: string) => {
270
// Would typically use useSWRMutation for sending
271
// This is just for demonstration
272
const socket = new WebSocket(`ws://localhost:3001/chat/${roomId}`);
273
socket.onopen = () => {
274
socket.send(JSON.stringify({ message, roomId }));
275
socket.close();
276
};
277
};
278
279
if (error) return <div>Failed to connect to chat</div>;
280
281
return (
282
<div>
283
<div>
284
{messages?.map(msg => (
285
<div key={msg.id}>
286
<strong>{msg.user}:</strong> {msg.message}
287
</div>
288
))}
289
</div>
290
291
<MessageInput onSend={sendMessage} />
292
</div>
293
);
294
}
295
```
296
297
**Live Dashboard:**
298
299
```typescript
300
interface DashboardData {
301
activeUsers: number;
302
revenue: number;
303
orders: number;
304
timestamp: number;
305
}
306
307
function LiveDashboard() {
308
const { data: stats, error } = useSWRSubscription(
309
"/api/dashboard/live",
310
(key, { next }) => {
311
const eventSource = new EventSource(key);
312
313
eventSource.onmessage = (event) => {
314
const newStats: DashboardData = JSON.parse(event.data);
315
next(null, newStats);
316
};
317
318
eventSource.onerror = () => {
319
next(new Error("Connection to live dashboard failed"));
320
};
321
322
return () => eventSource.close();
323
}
324
);
325
326
if (error) return <div>Error: {error.message}</div>;
327
if (!stats) return <div>Connecting...</div>;
328
329
return (
330
<div>
331
<h1>Live Dashboard</h1>
332
<div className="stats">
333
<div>Active Users: {stats.activeUsers}</div>
334
<div>Revenue: ${stats.revenue}</div>
335
<div>Orders: {stats.orders}</div>
336
<div>Last Update: {new Date(stats.timestamp).toLocaleTimeString()}</div>
337
</div>
338
</div>
339
);
340
}
341
```
342
343
**Stock Price Tracker:**
344
345
```typescript
346
interface StockPrice {
347
symbol: string;
348
price: number;
349
change: number;
350
changePercent: number;
351
timestamp: number;
352
}
353
354
function StockTracker({ symbols }: { symbols: string[] }) {
355
const { data: prices, error } = useSWRSubscription(
356
symbols.length > 0 ? ["stocks", ...symbols] : null,
357
([, ...symbols], { next }) => {
358
const ws = new WebSocket("wss://api.example.com/stocks");
359
360
ws.onopen = () => {
361
// Subscribe to symbols
362
ws.send(JSON.stringify({
363
action: "subscribe",
364
symbols: symbols
365
}));
366
};
367
368
ws.onmessage = (event) => {
369
const update: StockPrice = JSON.parse(event.data);
370
371
// Update prices map
372
next(null, (currentPrices: Record<string, StockPrice> = {}) => ({
373
...currentPrices,
374
[update.symbol]: update
375
}));
376
};
377
378
ws.onerror = () => {
379
next(new Error("Stock price feed connection failed"));
380
};
381
382
return () => {
383
ws.send(JSON.stringify({
384
action: "unsubscribe",
385
symbols: symbols
386
}));
387
ws.close();
388
};
389
}
390
);
391
392
if (error) return <div>Error: {error.message}</div>;
393
394
return (
395
<div>
396
<h2>Stock Prices</h2>
397
{symbols.map(symbol => {
398
const price = prices?.[symbol];
399
return (
400
<div key={symbol} className={price?.change >= 0 ? "positive" : "negative"}>
401
<span>{symbol}</span>
402
<span>${price?.price || "Loading..."}</span>
403
{price && (
404
<span>
405
{price.change >= 0 ? "+" : ""}{price.change}
406
({price.changePercent}%)
407
</span>
408
)}
409
</div>
410
);
411
})}
412
</div>
413
);
414
}
415
```
416
417
**Firebase Integration:**
418
419
```typescript
420
function UserPresence({ userId }: { userId: string }) {
421
const { data: presence } = useSWRSubscription(
422
userId ? ["firebase", "presence", userId] : null,
423
([, , userId], { next }) => {
424
const database = firebase.database();
425
const userRef = database.ref(`presence/${userId}`);
426
427
// Listen for presence changes
428
const listener = userRef.on("value", (snapshot) => {
429
const data = snapshot.val();
430
next(null, {
431
online: data?.online || false,
432
lastSeen: data?.lastSeen || null
433
});
434
});
435
436
// Set user as online
437
userRef.set({
438
online: true,
439
lastSeen: firebase.database.ServerValue.TIMESTAMP
440
});
441
442
// Set offline when disconnected
443
userRef.onDisconnect().set({
444
online: false,
445
lastSeen: firebase.database.ServerValue.TIMESTAMP
446
});
447
448
return () => {
449
userRef.off("value", listener);
450
userRef.set({
451
online: false,
452
lastSeen: firebase.database.ServerValue.TIMESTAMP
453
});
454
};
455
}
456
);
457
458
return (
459
<div>
460
Status: {presence?.online ? "Online" : "Offline"}
461
{presence?.lastSeen && !presence.online && (
462
<div>Last seen: {new Date(presence.lastSeen).toLocaleString()}</div>
463
)}
464
</div>
465
);
466
}
467
```
468
469
**Conditional Subscriptions:**
470
471
```typescript
472
function NotificationBell({ userId }: { userId?: string }) {
473
const [isEnabled, setIsEnabled] = useState(true);
474
475
const { data: notifications } = useSWRSubscription(
476
// Only subscribe if user is logged in and notifications are enabled
477
userId && isEnabled ? ["notifications", userId] : null,
478
([, userId], { next }) => {
479
const eventSource = new EventSource(`/api/notifications/${userId}/stream`);
480
481
eventSource.onmessage = (event) => {
482
const notification = JSON.parse(event.data);
483
484
// Accumulate notifications
485
next(null, (current: Notification[] = []) => [
486
notification,
487
...current.slice(0, 9) // Keep only latest 10
488
]);
489
};
490
491
return () => eventSource.close();
492
}
493
);
494
495
const unreadCount = notifications?.filter(n => !n.read).length || 0;
496
497
return (
498
<div>
499
<button onClick={() => setIsEnabled(!isEnabled)}>
500
๐ {unreadCount > 0 && <span className="badge">{unreadCount}</span>}
501
</button>
502
503
<div className="notifications">
504
{notifications?.map(notification => (
505
<div key={notification.id} className={notification.read ? "read" : "unread"}>
506
{notification.message}
507
</div>
508
))}
509
</div>
510
</div>
511
);
512
}
513
```