Presence
Presence
Presence lets you see who's online in a room and share custom state like user names, avatars, and activity status.
Basic Usage
import { usePresence } from '@realtime-sdk/react';
function OnlineUsers({ room }) {
const { users, self, others, updatePresence } = usePresence(room);
return (
<div>
<h3>Online ({users.length})</h3>
<ul>
{users.map(user => (
<li key={user.id} style={{ color: user.color }}>
{user.data.name ?? 'Anonymous'}
{user.id === self?.id && ' (you)'}
</li>
))}
</ul>
</div>
);
}
Presence Data
Each user has:
| Property | Type | Description |
|---|---|---|
id | string | Unique user ID (from JWT) |
color | string | Auto-assigned color (hex) |
data | object | Custom presence data |
cursor | CursorPosition | null | Current cursor position |
Setting Initial Presence
Pass initial presence when joining a room:
room.join({
metadata: {
name: user.name,
avatar: user.avatarUrl,
status: 'active',
},
});
Updating Presence
Update your presence data at any time:
const { updatePresence } = usePresence(room);
// Update single field
updatePresence({ status: 'away' });
// Update multiple fields
updatePresence({
name: 'New Name',
status: 'active',
});
Listening to Changes
The hook automatically re-renders when presence changes. For more control:
useEffect(() => {
const unsubJoin = room.on('presence:join', (user) => {
console.log(`${user.data.name} joined`);
});
const unsubLeave = room.on('presence:leave', (userId) => {
console.log(`User ${userId} left`);
});
return () => {
unsubJoin();
unsubLeave();
};
}, [room]);
Color Assignment
Each user is automatically assigned a color from a palette of 16 distinct colors. Colors are consistent for the duration of their session but may change on reconnection.
// Access the assigned color
const { self } = usePresence(room);
console.log(self?.color); // e.g., '#E57373'
Avatar Stack Component
Common pattern for showing online users:
function AvatarStack({ room }) {
const { others, self } = usePresence(room);
const maxVisible = 5;
const overflow = others.length - maxVisible;
return (
<div className="flex -space-x-2">
{self && (
<Avatar user={self} className="ring-2 ring-white z-10" />
)}
{others.slice(0, maxVisible).map((user, i) => (
<Avatar
key={user.id}
user={user}
style={{ zIndex: maxVisible - i }}
/>
))}
{overflow > 0 && (
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center text-xs">
+{overflow}
</div>
)}
</div>
);
}