@blinkdotnew/sdk 0.14.8 → 0.14.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1275,6 +1275,8 @@ All `{{secret_name}}` placeholders are replaced with encrypted values from your
|
|
|
1275
1275
|
The most common mistake is using async functions in useEffect that lose the cleanup function:
|
|
1276
1276
|
|
|
1277
1277
|
```typescript
|
|
1278
|
+
import type { RealtimeChannel } from '@blinkdotnew/sdk'
|
|
1279
|
+
|
|
1278
1280
|
// ❌ WRONG - Async function loses cleanup (causes "Subscription cancelled" errors)
|
|
1279
1281
|
useEffect(() => {
|
|
1280
1282
|
const initApp = async () => {
|
|
@@ -1298,7 +1300,9 @@ useEffect(() => {
|
|
|
1298
1300
|
```typescript
|
|
1299
1301
|
// ✅ CORRECT - Proper async cleanup handling
|
|
1300
1302
|
useEffect(() => {
|
|
1301
|
-
|
|
1303
|
+
if (!user?.id) return
|
|
1304
|
+
|
|
1305
|
+
let channel: RealtimeChannel | null = null
|
|
1302
1306
|
|
|
1303
1307
|
const initApp = async () => {
|
|
1304
1308
|
channel = blink.realtime.channel('room')
|
|
@@ -1311,32 +1315,78 @@ useEffect(() => {
|
|
|
1311
1315
|
return () => {
|
|
1312
1316
|
channel?.unsubscribe()
|
|
1313
1317
|
}
|
|
1314
|
-
}, [user
|
|
1318
|
+
}, [user?.id]) // ✅ Optional chaining in dependency too
|
|
1315
1319
|
```
|
|
1316
1320
|
|
|
1317
1321
|
```typescript
|
|
1318
1322
|
// ✅ ALTERNATIVE - Using state for cleanup
|
|
1319
|
-
const [channel, setChannel] = useState(null)
|
|
1323
|
+
const [channel, setChannel] = useState<RealtimeChannel | null>(null)
|
|
1320
1324
|
|
|
1321
1325
|
useEffect(() => {
|
|
1326
|
+
if (!user?.id) return
|
|
1327
|
+
|
|
1322
1328
|
const initApp = async () => {
|
|
1323
1329
|
const ch = blink.realtime.channel('room')
|
|
1324
1330
|
await ch.subscribe({ userId: user.id })
|
|
1325
1331
|
setChannel(ch)
|
|
1326
1332
|
}
|
|
1327
1333
|
initApp().catch(console.error)
|
|
1328
|
-
}, [user
|
|
1334
|
+
}, [user?.id])
|
|
1329
1335
|
|
|
1330
1336
|
useEffect(() => {
|
|
1331
1337
|
return () => channel?.unsubscribe()
|
|
1332
1338
|
}, [channel])
|
|
1333
1339
|
```
|
|
1334
1340
|
|
|
1341
|
+
```typescript
|
|
1342
|
+
// ✅ COMPLETE EXAMPLE - With proper loading states
|
|
1343
|
+
function MyRealtimeComponent() {
|
|
1344
|
+
const [user, setUser] = useState(null)
|
|
1345
|
+
const [messages, setMessages] = useState([])
|
|
1346
|
+
|
|
1347
|
+
// Auth state management
|
|
1348
|
+
useEffect(() => {
|
|
1349
|
+
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
|
|
1350
|
+
setUser(state.user)
|
|
1351
|
+
})
|
|
1352
|
+
return unsubscribe
|
|
1353
|
+
}, [])
|
|
1354
|
+
|
|
1355
|
+
// Guard clause - prevent rendering if user not loaded
|
|
1356
|
+
if (!user) return <div>Loading...</div>
|
|
1357
|
+
|
|
1358
|
+
// Now safe to use user.id everywhere
|
|
1359
|
+
useEffect(() => {
|
|
1360
|
+
if (!user?.id) return
|
|
1361
|
+
|
|
1362
|
+
let channel: RealtimeChannel | null = null
|
|
1363
|
+
|
|
1364
|
+
const initApp = async () => {
|
|
1365
|
+
channel = blink.realtime.channel('room')
|
|
1366
|
+
await channel.subscribe({ userId: user.id })
|
|
1367
|
+
|
|
1368
|
+
channel.onMessage((message) => {
|
|
1369
|
+
setMessages(prev => [...prev, message])
|
|
1370
|
+
})
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
initApp().catch(console.error)
|
|
1374
|
+
|
|
1375
|
+
return () => {
|
|
1376
|
+
channel?.unsubscribe()
|
|
1377
|
+
}
|
|
1378
|
+
}, [user?.id])
|
|
1379
|
+
|
|
1380
|
+
return <div>Welcome {user.email}! Messages: {messages.length}</div>
|
|
1381
|
+
}
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1335
1384
|
**Rules:**
|
|
1336
1385
|
1. **Never return cleanup from async functions** - useEffect cleanup must be synchronous
|
|
1337
|
-
2. **useEffect dependency**: `[user
|
|
1386
|
+
2. **useEffect dependency**: `[user?.id]` not `[user]` to avoid reconnections
|
|
1338
1387
|
3. **Store channel reference** outside async function for cleanup access
|
|
1339
|
-
4. **
|
|
1388
|
+
4. **Add component-level guards** - Check `if (!user) return <Loading />` before rendering
|
|
1389
|
+
5. **Zero connection management**: SDK handles all connection states automatically
|
|
1340
1390
|
|
|
1341
1391
|
### React
|
|
1342
1392
|
|
|
@@ -1462,15 +1512,23 @@ function RealtimeChat() {
|
|
|
1462
1512
|
const [onlineUsers, setOnlineUsers] = useState([])
|
|
1463
1513
|
const [user] = useState({ id: 'user123', name: 'John Doe' }) // From auth
|
|
1464
1514
|
|
|
1515
|
+
// Guard clause - prevent rendering if user not loaded
|
|
1516
|
+
if (!user) return <div>Loading...</div>
|
|
1517
|
+
|
|
1518
|
+
const userRef = useRef(user)
|
|
1519
|
+
useEffect(() => { userRef.current = user }, [user])
|
|
1520
|
+
|
|
1465
1521
|
useEffect(() => {
|
|
1466
|
-
|
|
1522
|
+
if (!user?.id) return
|
|
1523
|
+
|
|
1524
|
+
let channel: RealtimeChannel | null = null
|
|
1467
1525
|
|
|
1468
1526
|
// Subscribe and listen for messages
|
|
1469
1527
|
const setupRealtime = async () => {
|
|
1470
1528
|
channel = blink.realtime.channel('chat-room')
|
|
1471
1529
|
await channel.subscribe({
|
|
1472
|
-
userId:
|
|
1473
|
-
metadata: { displayName:
|
|
1530
|
+
userId: userRef.current.id,
|
|
1531
|
+
metadata: { displayName: userRef.current.name, avatar: '/avatar.png' }
|
|
1474
1532
|
})
|
|
1475
1533
|
|
|
1476
1534
|
// Listen for new messages
|
|
@@ -1513,7 +1571,7 @@ function RealtimeChat() {
|
|
|
1513
1571
|
return () => {
|
|
1514
1572
|
channel?.unsubscribe()
|
|
1515
1573
|
}
|
|
1516
|
-
}, [user
|
|
1574
|
+
}, [user?.id]) // ✅ Optional chaining in dependency
|
|
1517
1575
|
|
|
1518
1576
|
const sendMessage = async () => {
|
|
1519
1577
|
if (!newMessage.trim()) return
|
package/package.json
CHANGED