The useRealtime
hook connects your React components to realtime events with full type safety.
Basic Usage
Subscribe to events in any client component:
"use client"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function Page () {
useRealtime < RealtimeEvents >({
event: "notification.alert" ,
onData ( data , channel ) {
console . log ( "Received:" , data )
},
})
return < p > Listening for events... </ p >
}
Hook Options
The event to subscribe to (e.g. "notification.alert"
)
Callback when an event is received. Receives data
and channel
as arguments.
channels
string[]
default: "[\"default\"]"
Array of channel names to subscribe to
history
boolean | object
default: "false"
true
: Fetch all available history
{ length: number }
: Fetch the last N messages
{ since: number }
: Fetch messages after a Unix timestamp (in milliseconds)
Whether the connection is active. Set to false
to disconnect.
Maximum number of reconnection attempts before giving up
API configuration: - url
: The realtime endpoint URL, defaults to /api/realtime
-
withCredentials
: Whether to send cookies with requests (useful for external backends)
Return Value
The hook returns an object with:
Current connection state: "connecting"
, "connected"
, "reconnecting"
,
"disconnected"
, or "error"
const { status } = useRealtime < RealtimeEvents >({
event: "notification.alert" ,
onData : ( data , channel ) => {},
})
console . log ( status )
Connection Control
Enable or disable connections dynamically:
"use client"
import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function Page () {
const [ enabled , setEnabled ] = useState ( true )
const { status } = useRealtime < RealtimeEvents >({
enabled ,
event: "notification.alert" ,
onData : ( data , channel ) => {
console . log ( 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 "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
import { useUser } from "@/hooks/auth"
export default function Page () {
const { user } = useUser ()
useRealtime < RealtimeEvents >({
enabled: Boolean ( user ),
channels: [ `user- ${ user . id } ` ],
event: "notification.alert" ,
onData : ( data , channel ) => {
console . log ( data )
},
})
return < p > Notifications { user ? "enabled" : "disabled" } </ p >
}
Multiple Channels
Subscribe to multiple channels at once:
"use client"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function Page () {
useRealtime < RealtimeEvents >({
channels: [ "global" , "announcements" , "user-123" ],
event: "notification.alert" ,
onData ( 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 "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function Page () {
const [ channels , setChannels ] = useState < string []>([ "lobby" ])
useRealtime < RealtimeEvents >({
channels ,
event: "chat.message" ,
onData ( 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 >
)
}
Fetch History on Connection
Replay past messages when connecting:
"use client"
import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function ChatRoom () {
const [ messages , setMessages ] = useState < string []>([])
useRealtime < RealtimeEvents >({
event: "chat.message" ,
history: { length: 50 },
onData ( data , channel ) {
// each history item is automatically passed to this handler
// so you can replay with any logic you like
setMessages (( prev ) => [ ... prev , data ])
},
})
return (
< div >
{ messages . map (( msg , i ) => (
< p key = { i } > { msg } </ p >
)) }
</ div >
)
}
Custom API Endpoint
Configure a custom realtime endpoint:
"use client"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function Page () {
useRealtime < RealtimeEvents >({
event: "notification.alert" ,
api: {
url: "/api/custom-realtime" ,
withCredentials: true ,
},
onData : ( data , channel ) => {
console . log ( data )
},
})
return < p > Connected to custom endpoint </ p >
}
Use Cases
Show real-time notifications to users: "use client"
import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
import { toast } from "react-hot-toast"
import { useUser } from "@/hooks/auth"
export default function Notifications () {
const { user } = useUser ()
useRealtime < RealtimeEvents >({
channels: [ `user- ${ user . id } ` ],
event: "notification.alert" ,
onData ( content , channel ) {
toast ( content )
},
})
return < p > Listening for notifications... </ p >
}
Build a real-time chat: "use client"
import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
import z from "zod/v4"
type Message = z . infer < RealtimeEvents [ "chat" ][ "message" ]>
export default function Chat () {
const [ messages , setMessages ] = useState < Message []>([])
useRealtime < RealtimeEvents >({
channels: [ "room-123" ],
event: "chat.message" ,
history: true ,
onData ( message , channel ) {
setMessages (( prev ) => [ ... prev , message ])
},
})
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 "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
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 < RealtimeEvents >({
channels: [ "user-123" ],
event: "metrics.update" ,
onData () {
// 👇 invalidate, so react-query refetches
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 "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function Editor ({ documentId } : { documentId : string }) {
const [ content , setContent ] = useState ( "" )
useRealtime < RealtimeEvents >({
channels: [ `doc- ${ documentId } ` ],
event: "document.update" ,
history: { length: 1 },
onData ( data , channel ) {
setContent ( data . content )
},
})
return < textarea value = { content } onChange = { ( e ) => setContent ( e . target . value ) } />
}
Next Steps