Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

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.state with useChannelMembersState(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, online shows the value from when the channel loaded.
  • Don't add your own user.presence.changed listener. 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:

src/ChannelMembers.tsx
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>
  );
};
src/ChannelMembers.css
.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:

src/ChannelPanel.tsx
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:

  1. The server sends user.presence.changed with Bob's user, because the client watches a channel Bob is in with presence.
  2. The client puts the new user object into Bob's member in channel.state of every channel it holds with him.
  3. channel.state notifies its subscribers, and useChannelMembersState re-renders ChannelMembers with 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.