💬Intermediate
Realtime Chat
A live group chat app powered by WebSocket subscriptions. Messages sent by any user appear instantly for everyone connected — no polling, no page refresh.
ViteReactTypeScriptWebSocketsRealtime
How it works
1. Load
Fetch the last 50 messages with getList() on mount.
2. Subscribe
Open a WebSocket connection with subscribe(). Events arrive for every create and delete.
3. Update state
Add or remove messages from local state — React re-renders automatically.
subscribe() returns an unsubscribe function
Always clean up subscriptions in the useEffect return to avoid memory leaks. The unsubscribe function is async — call it with unsub?.().
Setup
cp .env.example .env
# VITE_TACOBASE_URL=https://your-app.tacobase.dev
# VITE_TACOBASE_API_KEY=tbk_...
npm install
npm run devThe realtime pattern
The core of the example. Load initial data, then subscribe and update local state on every event.
import { useClient } from '@tacobase/react'
function Chat({ userId }: { userId: string }) {
const client = useClient()
const [messages, setMessages] = useState<Message[]>([])
useEffect(() => {
let unsub: (() => Promise<void>) | null = null
async function init() {
// 1. Load recent messages
const initial = await client.collection('messages').getList(1, 50, {
sort: 'created',
})
setMessages(initial.items as Message[])
// 2. Subscribe to realtime updates
unsub = await client.collection('messages').subscribe((event) => {
if (event.action === 'create') {
setMessages(prev => [...prev, event.record as Message])
}
if (event.action === 'delete') {
setMessages(prev => prev.filter(m => m.id !== event.record.id))
}
})
}
init()
// Cleanup: unsubscribe on unmount
return () => { unsub?.() }
}, [client])
async function sendMessage(text: string) {
await client.collection('messages').create({ text, author: userId })
}
// ...
}