The useRealtime hook connects your React components to realtime events with full type safety.
Setup
1. Add the Provider
Wrap your app in the RealtimeProvider:
"use client"
import { RealtimeProvider } from "@upstash/realtime/client"
export function Providers({ children }: { children: React.ReactNode }) {
return <RealtimeProvider>{children}</RealtimeProvider>
}
import { Providers } from "./providers"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
2. Create Typed Hook
Create a typed useRealtime hook using createRealtime:
"use client"
import { createRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "./realtime"
export const { useRealtime } = createRealtime<RealtimeEvents>()
Basic Usage
Subscribe to events in any client component:
"use client"
import { useRealtime } from "@/lib/realtime-client"
export default function Page() {
useRealtime({
events: ["notification.alert"],
onData({ event, data, channel }) {
console.log(`Received ${event}:`, data)
},
})
return <p>Listening for events...</p>
}
Provider Options
API configuration: - url: The realtime endpoint URL - withCredentials: Whether to
send cookies with requests
Maximum number of reconnection attempts before giving up
"use client"
import { RealtimeProvider } from "@upstash/realtime/client"
export function Providers({ children }: { children: React.ReactNode }) {
return (
<RealtimeProvider
api={{ url: "/api/realtime", withCredentials: true }}
maxReconnectAttempts={5}
>
{children}
</RealtimeProvider>
)
}
Hook Options
Array of event names to subscribe to (e.g. ["notification.alert", "chat.message"])
Callback when an event is received. Receives an object with event, data, and
channel.
channels
string[]
default:"[\"default\"]"
Array of channel names to subscribe to
Whether the subscription is active. Set to false to disconnect.
Return Value
The hook returns an object with:
Current connection state: "connecting", "connected", "disconnected", or "error"
import { useRealtime } from "@/lib/realtime-client"
const { status } = useRealtime({
events: ["notification.alert"],
onData({ event, data, channel }) {},
})
console.log(status)
Connection Control
Enable or disable connections dynamically:
"use client"
import { useState } from "react"
import { useRealtime } from "@/lib/realtime-client"
export default function Page() {
const [enabled, setEnabled] = useState(true)
const { status } = useRealtime({
enabled,
events: ["notification.alert"],
onData({ event, data, channel }) {
console.log(event, data, channel)
},
})
return (
<div>
<button onClick={() => setEnabled((prev) => !prev)}>
{enabled ? "Disconnect" : "Connect"}
</button>
<p>Status: {status}</p>
</div>
)
}
Conditional Connections
Connect only when certain conditions are met:
"use client"
import { useRealtime } from "@/lib/realtime-client"
import { useUser } from "@/hooks/auth"
export default function Page() {
const { user } = useUser()
useRealtime({
enabled: Boolean(user),
channels: [`user-${user?.id}`],
events: ["notification.alert"],
onData({ event, data, channel }) {
console.log(data)
},
})
return <p>Notifications {user ? "enabled" : "disabled"}</p>
}
Multiple Events
Subscribe to multiple events at once:
"use client"
import { useRealtime } from "@/lib/realtime-client"
export default function Page() {
useRealtime({
events: ["chat.message", "chat.reaction", "user.joined"],
onData({ event, data, channel }) {
// 👇 data is automatically typed based on the event
if (event === "chat.message") console.log("New message:", data)
if (event === "chat.reaction") console.log("New reaction:", data)
if (event === "user.joined") console.log("User joined:", data)
},
})
return <p>Listening to multiple events</p>
}
Multiple Channels
Subscribe to multiple channels at once:
"use client"
import { useRealtime } from "@/lib/realtime-client"
export default function Page() {
useRealtime({
channels: ["global", "announcements", "user-123"],
events: ["notification.alert"],
onData({ event, data, channel }) {
console.log(`Message from ${channel}:`, data)
},
})
return <p>Listening to multiple channels</p>
}
Dynamic Channel Management
Add and remove channels dynamically:
"use client"
import { useState } from "react"
import { useRealtime } from "@/lib/realtime-client"
export default function Page() {
const [channels, setChannels] = useState<string[]>(["lobby"])
useRealtime({
channels,
events: ["chat.message"],
onData({ event, data, channel }) {
console.log(`Message from ${channel}:`, data)
},
})
const joinRoom = (roomId: string) => {
setChannels((prev) => [...prev, roomId])
}
const leaveRoom = (roomId: string) => {
setChannels((prev) => prev.filter((c) => c !== roomId))
}
return (
<div>
<p>Active channels: {channels.join(", ")}</p>
<button onClick={() => joinRoom("room-1")}>Join Room 1</button>
<button onClick={() => joinRoom("room-2")}>Join Room 2</button>
<button onClick={() => leaveRoom("lobby")}>Leave Lobby</button>
</div>
)
}
Custom API Endpoint
Configure a custom realtime endpoint in the provider:
"use client"
import { RealtimeProvider } from "@upstash/realtime/client"
export function Providers({ children }: { children: React.ReactNode }) {
return (
<RealtimeProvider
api={{
url: "/api/custom-realtime",
withCredentials: true,
}}
>
{children}
</RealtimeProvider>
)
}
Use Cases
Show real-time notifications to users:"use client"
import { useRealtime } from "@/lib/realtime-client"
import { toast } from "react-hot-toast"
import { useUser } from "@/hooks/auth"
export default function Notifications() {
const { user } = useUser()
useRealtime({
channels: [`user-${user.id}`],
events: ["notification.alert"],
onData({ data }) {
toast(data)
},
})
return <p>Listening for notifications...</p>
}
Build a real-time chat:"use client"
import { useState } from "react"
import { useRealtime } from "@/lib/realtime-client"
import z from "zod/v4"
import type { RealtimeEvents } from "@/lib/realtime"
type Message = z.infer<RealtimeEvents["chat"]["message"]>
export default function Chat() {
const [messages, setMessages] = useState<Message[]>([])
useRealtime({
channels: ["room-123"],
events: ["chat.message"],
onData({ data }) {
setMessages((prev) => [...prev, data])
},
})
return (
<div>
{messages.map((msg, i) => (
<p key={i}>
<span className="font-bold">{msg.sender}:</span> {msg.text}
</p>
))}
</div>
)
}
Update metrics in real-time:"use client"
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { useRealtime } from "@/lib/realtime-client"
export default function Dashboard() {
const queryClient = useQueryClient()
const { data: metrics } = useQuery({
queryKey: ["metrics"],
queryFn: async () => {
const res = await fetch("/api/metrics?user=user-123")
return res.json()
},
})
useRealtime({
channels: ["user-123"],
events: ["metrics.update"],
onData() {
queryClient.invalidateQueries({ queryKey: ["metrics"] })
},
})
return (
<div>
<p>Active Users: {metrics?.users}</p>
<p>Revenue: ${metrics?.revenue}</p>
</div>
)
}
Sync changes across users:"use client"
import { useState } from "react"
import { useRealtime } from "@/lib/realtime-client"
export default function Editor({ documentId }: { documentId: string }) {
const [content, setContent] = useState("")
useRealtime({
channels: [`doc-${documentId}`],
events: ["document.update"],
onData({ data }) {
setContent(data.content)
},
})
return <textarea value={content} onChange={(e) => setContent(e.target.value)} />
}
Next Steps