# Quick start

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

This page builds a chat app in four steps. Each step is a complete `src/App.tsx`. For a guided version, follow the [React Chat tutorial](https://getstream.io/chat/sdk/react/tutorial/).

Before you start:

- Install `stream-chat` and `stream-chat-react` as described in [Installation](https://getstream.io/chat/docs/sdk/react/v15/getting-started/installation/).
- Create a Stream Chat app and [generate a token](https://getstream.io/chat/docs/react/tokens-and-authentication/) for a user. The examples use its API key, the user's ID and the token in place of `YOUR_API_KEY`, `YOUR_USER_ID` and `YOUR_USER_TOKEN`.

## Create the client

```tsx title="src/App.tsx"
import { Chat, useCreateChatClient } from "stream-chat-react";
import "stream-chat-react/dist/css/index.css";

const apiKey = "YOUR_API_KEY";
const userId = "YOUR_USER_ID";
const userToken = "YOUR_USER_TOKEN";

const App = () => {
  const client = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    userData: { id: userId },
  });

  if (!client) return <div>Connecting...</div>;

  return <Chat client={client}>The client is connected.</Chat>;
};

export default App;
```

`useCreateChatClient` creates a `StreamChat` client and connects the user. It returns `null` until the connection is open, and then the client. When the component unmounts, it disconnects the user.

`Chat` gives the client to every SDK component inside it. Your own components read it with `useChatContext()`.

The hook creates a new client when `apiKey`, the user's `id` or `tokenOrProvider` changes. So a token provider function must not be re-created on every render: memoize it with `useCallback`, or declare it outside the component.

To configure the client and the objects it creates, pass a `config` option to `useCreateChatClient`. It takes the same tree as `client.config.set()`, and the hook reads it once, on the first render. This one turns on message drafts:

```tsx title="src/App.tsx" add="12"
import { Chat, useCreateChatClient } from "stream-chat-react";
import "stream-chat-react/dist/css/index.css";

const apiKey = "YOUR_API_KEY";
const userId = "YOUR_USER_ID";
const userToken = "YOUR_USER_TOKEN";

const App = () => {
  const client = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    config: { messageComposer: { drafts: { enabled: true } } },
    userData: { id: userId },
  });

  if (!client) return <div>Connecting...</div>;

  return <Chat client={client}>The client is connected.</Chat>;
};

export default App;
```

The settings and their defaults are on [Configuration](https://getstream.io/chat/docs/sdk/react/v15/state/state-and-entities/configuration/).

## Show one channel

A single channel needs no layout. Get the channel from the client, watch it, and pass it to `Channel`:

```tsx title="src/App.tsx"
import { useEffect, useState } from "react";
import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  ChannelHeader,
  Chat,
  MessageComposer,
  MessageList,
  useCreateChatClient,
} from "stream-chat-react";
import "stream-chat-react/dist/css/index.css";
import "./layout.css";

const apiKey = "YOUR_API_KEY";
const userId = "YOUR_USER_ID";
const userToken = "YOUR_USER_TOKEN";

const App = () => {
  const client = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    userData: { id: userId },
  });
  const [channel, setChannel] = useState<StreamChannel>();

  useEffect(() => {
    if (!client) return;
    let cancelled = false;

    // Watching loads the channel and subscribes to its events. The server creates
    // the channel if it doesn't exist yet.
    client
      .channel("messaging", "react-quick-start", {
        members: [{ user_id: userId }],
      })
      .watch()
      .then((watchedChannel) => {
        if (!cancelled) setChannel(watchedChannel);
      })
      .catch((error) => console.error("Could not watch the channel", error));

    return () => {
      cancelled = true;
      setChannel(undefined);
    };
  }, [client]);

  if (!client) return <div>Connecting...</div>;
  if (!channel) return <div>Loading the channel...</div>;

  return (
    <Chat client={client}>
      <Channel channel={channel}>
        <ChannelHeader />
        <MessageList />
        <MessageComposer />
      </Channel>
    </Chat>
  );
};

export default App;
```

```css title="src/layout.css"
html,
body,
#root {
  height: 100%;
}

body {
  margin: 0;
}
```

Here is what happens:

1. `client.channel("messaging", "react-quick-start", ...)` returns the object for that channel. The client keeps one object per channel, so a second call returns the same one.
2. `Channel.watch()` watches the channel: it loads the messages, members and read state, and subscribes to the channel's events. The server creates the channel the first time. Calling it again is safe, even from several places at once: it sends a request only when the channel isn't loaded yet, or when you pass options it wasn't watched with. So it's safe in code that can run more than once, such as this effect. The promise resolves with the channel object to use from then on. See [watch and query](https://getstream.io/chat/docs/sdk/react/v15/state/state-and-entities/shared-entities/#watch-and-query).
3. `Channel` gives the channel to the components inside it. It loads nothing itself, so the app renders it once the channel is watched. A component inside reads the channel with `useChannel()`.

The channel type, `messaging`, decides which features and permissions the channel has. See [channel types](https://getstream.io/chat/docs/react/channel-features/).

`layout.css` makes the page fill the window, so the channel can fill the page. `#root` is the element the app renders into.

Without a layout, a thread has nowhere to open, so the reply button on a message does nothing. The next steps add `ChatView`, which places channels and threads.

## Add a channel list with ChatView

Channels are queried from server and kept in lists matching the filters and a sort order by controllers called `ChannelPaginator`. Registered on `client.channelManager`, the lists are kept up to date as messages and other events arrive. In the sample below, `ChannelNavigation` renders a `ChannelList` for single registered paginator. The paginator loads its first page when `ChannelList` mounts.

Clicking a channel in the list opens it. Where it opens is up to the layout, `ChatView` from `stream-chat-react/slot-layout`:

```tsx title="src/App.tsx"
import { useEffect } from "react";
import { ChannelPaginator } from "stream-chat";
import type { ChannelFilters, SortParamRequest } from "stream-chat";
import {
  ChannelHeader,
  ChannelNavigation,
  Chat,
  MessageComposer,
  MessageList,
  useCreateChatClient,
} from "stream-chat-react";
import { ChannelSlot, ChatView } from "stream-chat-react/slot-layout";
import type { LayoutDescriptor } from "stream-chat-react/slot-layout";
import "stream-chat-react/dist/css/index.css";
import "./layout.css";

const apiKey = "YOUR_API_KEY";
const userId = "YOUR_USER_ID";
const userToken = "YOUR_USER_TOKEN";

// The user's messaging channels, latest message first.
const filters: ChannelFilters = {
  members: { $in: [userId] },
  type: "messaging",
};
const sort: SortParamRequest[] = [{ direction: -1, field: "last_message_at" }];

// The channels view has two slots: "channel" for the channel messages, and "thread"
// for a message replies opened from it. The variable layouts is declared outside
// the component, so ChatView gets the same array on every render.
const layouts: LayoutDescriptor[] = [
  { id: "channels", slots: ["channel", "thread"] },
];

const ChannelsView = () => (
  <>
    {/* The list header, the search bar and a ChannelList for each registered paginator */}
    <ChannelNavigation />
    <ChannelSlot slot="channel">
      <ChannelHeader />
      <MessageList />
      <MessageComposer />
    </ChannelSlot>
  </>
);

const App = () => {
  const client = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    userData: { id: userId },
  });

  // Registers the list on the channel manager, which keeps it up to date.
  useEffect(() => {
    if (!client) return;
    const paginator = new ChannelPaginator({
      client,
      filters,
      id: "my-channels",
      sort,
    });
    client.channelManager.insertPaginator({ paginator });
    return () => {
      client.channelManager.removePaginator(paginator);
    };
  }, [client]);

  if (!client) return <div>Connecting...</div>;

  return (
    <Chat client={client}>
      <ChatView layouts={layouts} views={{ channels: <ChannelsView /> }} />
    </Chat>
  );
};

export default App;
```

```css title="src/layout.css"
html,
body,
#root {
  height: 100%;
}

body {
  margin: 0;
}

.str-chat {
  --str-chat__channel-list-width: 320px;
}
```

Here is what happens when the user clicks a channel:

1. `ChannelNavigation` renders a stack of the channel list header, the search bar and one `ChannelList` for each paginator registered on the channel manager. The effect registers one paginator, `"my-channels"`, so there is one list. A row in the list calls `openChannel(channel)` from `useWorkspaceNavigation()` when clicked.
2. `ChatView` puts the channel in a slot of the channels view. `layouts` declares the slots: the slot called `"channel"` gets populated with the data of the channel selected from the list. The `ChannelSlot` is the slot component that declares itself as a target slot via `slot` property (`slot='channel'`).

The `thread` slot is where a thread opens. The next step renders it. Declaring it now keeps a thread from opening in the `channel` slot, in place of the channel.

The CSS variable gives the channel list a fixed width, and the channel takes the rest. On screens narrower than 768px, the SDK's stylesheet hides the channel list, and an app shows it there itself, for example as an overlay. Try this step in a wider window.

## Add threads

Threads open in two places: beside the channel, when the user opens the replies to a message, and in a threads view that lists the user's threads. This step adds both:

```tsx title="src/App.tsx"
import { useEffect } from "react";
import { ChannelPaginator } from "stream-chat";
import type { ChannelFilters, SortParamRequest } from "stream-chat";
import {
  ChannelHeader,
  ChannelNavigation,
  Chat,
  MessageComposer,
  MessageList,
  ThreadHeader,
  ThreadList,
  useCreateChatClient,
} from "stream-chat-react";
import {
  ChannelSlot,
  ChatView,
  ThreadSlot,
} from "stream-chat-react/slot-layout";
import type { LayoutDescriptor } from "stream-chat-react/slot-layout";
import "stream-chat-react/dist/css/index.css";
import "./layout.css";

const apiKey = "YOUR_API_KEY";
const userId = "YOUR_USER_ID";
const userToken = "YOUR_USER_TOKEN";

const filters: ChannelFilters = {
  members: { $in: [userId] },
  type: "messaging",
};
const sort: SortParamRequest[] = [{ direction: -1, field: "last_message_at" }];

// The channels view has a slot for the open channel and one for a thread opened
// from it. The threads view has one slot, for the thread picked in the list.
const layouts: LayoutDescriptor[] = [
  { id: "channels", slots: ["channel", "thread"] },
  { id: "threads", slots: ["thread"] },
];

// The thread in the current view's "thread" slot, or nothing while none is open.
const ThreadPanel = () => (
  <ThreadSlot slot="thread">
    <ThreadHeader />
    <MessageList />
    <MessageComposer />
  </ThreadSlot>
);

const ChannelsView = () => (
  <>
    <ChatView.Selector />

    <ChannelNavigation />

    <ChannelSlot slot="channel">
      <ChannelHeader />
      <MessageList />
      <MessageComposer />
    </ChannelSlot>

    <ThreadPanel />
  </>
);

const ThreadsView = () => (
  <>
    <ChatView.Selector />
    <ThreadList />
    <ThreadPanel />
  </>
);

const App = () => {
  const client = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    userData: { id: userId },
  });

  useEffect(() => {
    if (!client) return;
    const paginator = new ChannelPaginator({
      client,
      filters,
      id: "my-channels",
      sort,
    });
    client.channelManager.insertPaginator({ paginator });
    return () => {
      client.channelManager.removePaginator(paginator);
    };
  }, [client]);

  if (!client) return <div>Connecting...</div>;

  return (
    <Chat client={client}>
      <ChatView
        layouts={layouts}
        views={{ channels: <ChannelsView />, threads: <ThreadsView /> }}
      />
    </Chat>
  );
};

export default App;
```

Give the thread list a fixed width too:

```css title="src/layout.css"
html,
body,
#root {
  height: 100%;
}

body {
  margin: 0;
}

.str-chat {
  --str-chat__channel-list-width: 320px;
  --str-chat__thread-list-width: 320px;
}
```

What changed:

- `ThreadSlot` captures all the requests to open slot `"thread"` and renders the thread UI. While the slot is empty, it renders nothing. It provides `Thread` instance to all the child components.
- The `threads` view has its own `thread` slot and thread listing through `ThreadList`.
- `ChatView.Selector` renders the buttons that switch between the channels view and the threads view. The threads button shows the number of unread threads.

The emoji picker is optional. It comes from `stream-chat-react/emojis` and needs the `emoji-mart` and `@emoji-mart/data` packages. See [Emoji](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/emoji/).

## Next steps

- [How the SDK is built](https://getstream.io/chat/docs/sdk/react/v15/getting-started/how-the-sdk-is-built/): What happens in the state and UI layers when a user opens a channel and sends a message.
- [ChatView components](https://getstream.io/chat/docs/sdk/react/v15/component-reference/layout/chatview-components/): Views, slots and the components that fill them.
- [Channel list paginators](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/channel-list-paginators/): Several channel lists, each with its own filters.
- [Theming](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/theming/): Style the components with CSS variables.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/getting-started/quick-start/](https://getstream.io/chat/docs/sdk/react/v15/getting-started/quick-start/).