# Members and online status

This recipe renders the members of the open channel with their online status.

>
> **Note:** **What you'll build**
>
> - A member list for the open channel
> - A green dot beside each member who is online
> - A list that updates as members come online, go offline, join or leave
>

---

**Preview**

![The channel header above a list of the channel members, with a green dot for each member who is online](https://getstream.io/docs-assets/images/836971d3d3f2.png)

---

**Approach:** Compose. [Choosing where to start](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/overview/#choosing-where-to-start) explains the approaches.

**Starts from:** [the base app](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/overview/#the-base-app). The page shows only what the recipe adds.

## 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, `member.user.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](https://getstream.io/chat/docs/sdk/react/features/channels/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`:

```tsx label="React" title="src/ChannelMembers.tsx"
import {
  useChannel,
  useChannelMembersState,
  VisuallyHidden,
} from "stream-chat-react";
import "./ChannelMembers.css";

export const ChannelMembers = () => {
  const channel = useChannel();
  const members = useChannelMembersState(channel);

  return (
    <ul className="cookbook-channel-members">
      {Object.values(members).map((member) => (
        <li className="cookbook-channel-members__item" key={member.user_id}>
          <span
            aria-hidden
            className={
              member.user?.online
                ? "cookbook-channel-members__dot cookbook-channel-members__dot--online"
                : "cookbook-channel-members__dot"
            }
          />
          {member.user?.name ?? member.user_id}
          <VisuallyHidden>
            {member.user?.online ? ", online" : ", offline"}
          </VisuallyHidden>
        </li>
      ))}
    </ul>
  );
};
```

```css label="CSS" title="src/ChannelMembers.css"
@layer stream-overrides {
  .cookbook-channel-members {
    display: flex;
    flex-wrap: wrap;
    gap: var(--str-chat__spacing-xs);
    margin: 0;
    padding: var(--str-chat__spacing-sm) var(--str-chat__spacing-md);
    border-bottom: 1px solid var(--str-chat__border-core-default);
    background: var(--str-chat__background-core-elevation-1);
    list-style: none;
  }

  .cookbook-channel-members__item {
    display: flex;
    align-items: center;
    gap: var(--str-chat__spacing-xs);
    padding: var(--str-chat__spacing-xxs) var(--str-chat__spacing-sm);
    border-radius: var(--str-chat__radius-full);
    background: var(--str-chat__background-core-surface-subtle);
    color: var(--str-chat__text-primary);
    font: var(--str-chat__font-caption-default);
  }

  .cookbook-channel-members__dot {
    width: 8px;
    height: 8px;
    border-radius: var(--str-chat__radius-full);
    background: var(--str-chat__presence-bg-offline);
  }

  .cookbook-channel-members__dot--online {
    background: var(--str-chat__presence-bg-online);
  }
}
```

The dot is only decoration for screen readers, so it is `aria-hidden`; the SDK's `VisuallyHidden` adds the status as text that screen readers read after the name and the page doesn't show.

The rules sit in the `stream-overrides` layer, so they win over the SDK's own rules once the SDK's stylesheet is in an earlier layer, as [Theming → CSS layers](https://getstream.io/chat/docs/sdk/react/features/app-experience/theming/#css-layers) sets up. They take their colors, spacing and type from the SDK's tokens, so they follow your theme: the dots use the presence colors, `--str-chat__presence-bg-online` and `--str-chat__presence-bg-offline`, that the SDK's avatars use.

Render it inside `Channel`, next to the other parts of the channel:

```tsx title="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](https://getstream.io/chat/docs/sdk/react/state/channels-and-threads/what-channels-and-threads-hold/#data-and-members) lists the rest of `channel.state`.

## Watch the channel with presence

The server sends presence changes only for channels watched with presence. Ask for it where the channel is loaded:

```ts
// 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](https://getstream.io/chat/docs/sdk/react/state/loading-data/channel-list-paginators/#creating-a-channel-paginator) explains `requestOptions`, and [User presence](https://getstream.io/chat/docs/javascript/presence-format/) covers presence on the server.

## How it works

The list updates by itself.

>
> **Info:** **Example**
>
> 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's `member.user.online` set to `true`.
>

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.

## Related

- [What channels and threads hold](https://getstream.io/chat/docs/sdk/react/state/channels-and-threads/what-channels-and-threads-hold/#data-and-members): `channel.state.members` and the rest of `channel.state`.
- [Channel list paginators](https://getstream.io/chat/docs/sdk/react/state/loading-data/channel-list-paginators/#creating-a-channel-paginator): `requestOptions` on a channel paginator.
- [Reading state in components](https://getstream.io/chat/docs/sdk/react/ui/reading-state-in-components/): how hooks such as `useChannelMembersState` subscribe to a store.
- [Channel details and members](https://getstream.io/chat/docs/sdk/react/features/channels/channel-details-and-members/): the full member list of a large channel.
- [User presence](https://getstream.io/chat/docs/javascript/presence-format/): presence on the server.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/ui-cookbooks/channel-list/members-and-online-status/](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/channel-list/members-and-online-status/).