Members and online status
This recipe renders the members of the open channel with their online status, and keeps the list current as members come online, go offline, join or leave.
Best practices
- Read members from
channel.statewithuseChannelMembersState(channel). It subscribes to the store, so the component re-renders when the members change. - Watch the channel with presence, so the server sends presence changes. Without it,
onlineshows the value from when the channel loaded. - Don't add your own
user.presence.changedlistener. The client already writes each presence change into the members it holds. - Keep the list simple. With presence on, it re-renders every time a member comes online or goes offline.
- Expect only some members in a large channel. A channel loads a limited number of members with its query; see Channel details and members for the full member list.
Render the channel members list
The component reads the channel from Channel with useChannel(), and its members with useChannelMembersState:
import { useChannel, useChannelMembersState } from "stream-chat-react";
import "./ChannelMembers.css";
export const ChannelMembers = () => {
const channel = useChannel();
const members = useChannelMembersState(channel);
return (
<ul className="channel-members">
{Object.values(members).map((member) => (
<li className="channel-members__item" key={member.user_id}>
<span
className={
member.user?.online
? "channel-members__dot channel-members__dot--online"
: "channel-members__dot"
}
/>
{member.user?.name ?? member.user_id}
</li>
))}
</ul>
);
};.channel-members {
margin: 0;
padding: 12px 16px;
border-bottom: 1px solid #ecebeb;
list-style: none;
}
.channel-members__item {
display: flex;
align-items: center;
gap: 8px;
padding-block: 4px;
}
.channel-members__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #b4b7bb;
}
.channel-members__dot--online {
background: #20e070;
}Render it inside Channel, next to the other parts of the channel:
import type { Channel as StreamChannel } from "stream-chat";
import {
Channel,
ChannelHeader,
MessageComposer,
MessageList,
} from "stream-chat-react";
import { ChannelMembers } from "./ChannelMembers";
export const ChannelPanel = ({ channel }: { channel: StreamChannel }) => (
<Channel channel={channel}>
<ChannelHeader />
<ChannelMembers />
<MessageList />
<MessageComposer />
</Channel>
);With ChatView, put ChannelMembers among the children of ChannelSlot instead: the slot renders them inside the Channel it opens.
channel.state.members holds the loaded members by user id. Each member carries its user, with name, image, online and last_active. What channels and threads hold lists the rest of channel.state.
Real-time updates
The list updates by itself. When Bob comes online:
- The server sends
user.presence.changedwith Bob's user, because the client watches a channel Bob is in with presence. - The client puts the new user object into Bob's member in
channel.stateof every channel it holds with him. channel.statenotifies its subscribers, anduseChannelMembersStatere-rendersChannelMemberswith Bob online.
A member who joins, leaves or is updated changes the same store, through member.added, member.removed and member.updated, so the list follows those too.
The server sends presence changes only for channels watched with presence. Ask for it where the channel is loaded:
// A channel list: every channel it loads is watched with presence.
const teamChannels = new ChannelPaginator({
client,
filters: { members: { $in: [userId] }, type: "team" },
id: "team",
requestOptions: { presence: true },
});
// A channel you watch yourself.
const support = await client
.channel("messaging", "support")
.watch({ presence: true });Channel.watch({ presence: true }) sends a request even for a channel a channel list already watched without presence, because the current watch wasn't sent with these options. Later calls with the same options send nothing. Channel list paginators explains requestOptions, and User presence covers presence on the server.