or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cache-management.mdcore-data-fetching.mdglobal-configuration.mdimmutable-data.mdindex.mdinfinite-loading.mdmutations.mdsubscriptions.md

subscriptions.mddocs/

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

```