@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.
Files changed (2) hide show
  1. package/README.md +68 -10
  2. 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
- let channel = null
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.id]) // ✅ Only user.id dependency
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.id])
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.id]` not `[user]` to avoid reconnections
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. **Zero connection management**: SDK handles all connection states automatically
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
- let channel = null
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: user.id,
1473
- metadata: { displayName: user.name, avatar: '/avatar.png' }
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.id, user.name])
1574
+ }, [user?.id]) // ✅ Optional chaining in dependency
1517
1575
 
1518
1576
  const sendMessage = async () => {
1519
1577
  if (!newMessage.trim()) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "0.14.8",
3
+ "version": "0.14.9",
4
4
  "description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps",
5
5
  "keywords": [
6
6
  "blink",