Learn how to quickly integrate rich Generative AI experiences directly into Stream Chat. Learn More ->

React Chat Tutorial

The following tutorial shows you how to quickly build a chat app leveraging Stream's Chat API and the Stream Chat React components. The underlying API is very flexible and allows you to build nearly any type of chat experience.

example of react chat sdk

Stream's React Chat messaging SDK component library includes everything you need to build a fully functioning chat experience, with support for rich messages, reactions, threads, image uploads, videos, and more. This library is designed to enable you to get an application up and running quickly and efficiently while supporting customization for complex use cases.

We’ve included a few Stream React Chat examples to show you what’s possible:

  • Basic Chat Components
  • ChannelList
  • Customizable UI Components
  • Custom Attachment Component

Project Setup and Installation

The easiest way to build a Stream Chat React JS application from this tutorial is to create a new project using Vite. Vite allows you create a boilerplate React application that you can run locally with just a few simple commands.

Create a new React project called chat-example with Vite using the TypeScript template:

shell
1
2
3
npm create vite chat-example -- --template react-ts cd chat-example npm i stream-chat stream-chat-react

To make the tutorial as easy as possible each of our code samples in this tutorial comes with generated credentials for you to pick-up and use, these credentials consist of:

  • apiKey - an API key that is used to identify your Stream application by our servers
  • userId and userToken - authorization information of the current chat user
  • userName - optional, used as a display name of the current chat user

Now if you already have and wish to use your own Stream application, you can certainly do so - feel free to use our token (JWT) generator utility to easily generate authorization token for your user. Learn more about authentication in our Tokens & Authentication documentation.

Client Setup

Before we begin working with the chat UI components we'll need to set up a StreamChat client which abstracts API calls into methods, handles state and real-time events. To make the instantiation and connection handling easier, we've prepared a simple hook (useCreateChatClient) that you can use in your application. Let's begin by replacing the contents of the generated src/App.tsx file with this code snippet:

Note: Make sure you use the useCreateChatClient hook only once per application. If you need the client instance somewhere down in the component tree use the useChatContext hook (exported by the stream-chat-react) to access it.

App.tsx (tsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Chat, useCreateChatClient } from 'stream-chat-react'; // your Stream app information const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userName = 'REPLACE_WITH_USER_NAME'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const App = () => { const client = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: { id: userId }, }); if (!client) return <div>Setting up client & connection...</div>; return <Chat client={client}>Chat with client is ready!</Chat>; }; export default App;

Note: The client has to have a connection established before you can pass it down to the Chat component otherwise it won't work.

If this hook somehow gets in a way you can certainly adjust the client setup to your liking, see useCreateChatClient source for inspiration.

Start your application with yarn dev or npm dev and navigate to the site generated by this command. In the network tab of the developer tools you should be able to see secure websocket connection to our servers if everything was set up correctly.

Initial Core Component Setup

Now that the client connection has been established it's time to actually make the application interactable. To do this we'll create a base setup which we'll be modifying and expanding on later in the tutorial. This initial setup is built using these core components:

Let's extend our src/App.tsx code with this new setup:

App.tsx (tsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { useState, useEffect } from 'react'; import type { User, Channel as StreamChannel } from 'stream-chat'; import { useCreateChatClient, Chat, Channel, ChannelHeader, MessageInput, MessageList, Thread, Window } from 'stream-chat-react'; import 'stream-chat-react/dist/css/v2/index.css'; const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userName = 'REPLACE_WITH_USER_NAME'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; const App = () => { const [channel, setChannel] = useState<StreamChannel>(); const client = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: user, }); useEffect(() => { if (!client) return; const channel = client.channel('messaging', 'custom_channel_id', { image: 'https://getstream.io/random_png/?name=react', name: 'Talk about React', members: [userId], }); setChannel(channel); }, [client]); if (!client) return <div>Setting up client & connection...</div>; return ( <Chat client={client}> <Channel channel={channel}> <Window> <ChannelHeader /> <MessageList /> <MessageInput /> </Window> <Thread /> </Channel> </Chat> ); }; export default App;

You might've noticed that the application does not look very appealing out of the box - that is because we leave layouting for integrators to position their components the way they desire. But for now, let's create a CSS file (src/layout.css) with a basic layout:

css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
html, body, #root { height: 100%; } body { margin: 0; } #root { display: flex; } .str-chat__channel-list { width: 30%; } .str-chat__channel { width: 100%; } .str-chat__thread { width: 45%; }

And then we'll import it in src/App.tsx right below stream-chat-react CSS import:

tsx
1
2
3
// other imports import 'stream-chat-react/dist/css/v2/index.css'; import './layout.css';

Note: Be sure to get rid of the Vite's default src/index.css imports to make sure your styling isn't getting messed up with unwanted rules.

The Chat and Channel components are React context providers that pass a variety of values to their children, including UI components, channel state data, and messaging functions.

Note how you create a channel with the channel method available on the StreamChat client instance. The first argument is the channel type messaging; the second argument is optional and specifies either the channel identificator or custom identificator.

The channel type determines the enabled features and permissions associated with this channel. The channel identificator is a unique reference to this specific channel.

Once you have the app running, you’ll notice the following out-of-the-box features:

  • User online presence
  • Typing indicators
  • Message status indicators (sending, received)
  • User role configuration
  • Emoji support (opt-in)
  • Message read indicators
  • Threading and message replies
  • Message reactions
  • URL previews (send a YouTube link to see this in action)
  • File uploads and previews
  • Video playback
  • Autocomplete-enabled search on users, emojis (opt-in), and commands
  • Slash commands such as /giphy (custom commands are also supported)
  • AI-powered spam and profanity moderation

Add a Channel List

ChannelList component displays a list of channel previews. It internally loads the data about the channels relevant to our user. The channels are loaded according to the criteria specified through its props. These props are:

  • filter: applies a filter to the query which loads the channels, minimal filter should include channel type and membership to load channels related only to the connected user (see example)
  • sort: applies a sorting criteria to the channels selected by the filter, ideally we want to sort the channels by the time of the last message
  • options: applies some additional options to the query, in this case - limit the number of channels we load to 10

Note: You can read more in the ChannelList documentation.

Let's adjust our code to render a list of channels through the use of the ChannelList component:

react-chat+ChannelList (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import type { User, ChannelSort, ChannelFilters, ChannelOptions } from 'stream-chat'; import { useCreateChatClient, Chat, Channel, ChannelHeader, ChannelList, MessageInput, MessageList, Thread, Window, } from 'stream-chat-react'; import 'stream-chat-react/dist/css/v2/index.css'; import './layout.css'; const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userName = 'REPLACE_WITH_USER_NAME'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; const sort: ChannelSort = { last_message_at: -1 }; const filters: ChannelFilters = { type: 'messaging', members: { $in: [userId] }, }; const options: ChannelOptions = { limit: 10, }; const App = () => { const client = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: user, }); if (!client) return <div>Setting up client & connection...</div>; return ( <Chat client={client}> <ChannelList filters={filters} sort={sort} options={options} /> <Channel> <Window> <ChannelHeader /> <MessageList /> <MessageInput /> </Window> <Thread /> </Channel> </Chat> ); }; export default App;

You'll notice that we've removed channel instantiation part as the channels are now handled by the ChannelList component which will set the first channel in the list as active automatically. This does not mean that you cannot use the previous code sample to programatically set your channels anymore - it's just not needed in this specific example.

Theming

Theming in React SDK is done through CSS variables which we've listed in our theming documentation. We've prepared two default themes (light str-chat__theme-light and dark - str-chat__theme-dark) for you to use but you can create as many as you'd like or even override the default ones. Let's take a look at how'd one build their own simple theme.

Now, let's move our stream-chat-react CSS import from src/App.tsx to src/layout.css stylesheet and add it to the layer with name base. Let's also create our new theme and add it to the layer with name theme. Layers are important as we're defining specificity in which the browser should load the styles. Our src/layout.scss should now look something like this:

react-chat+Theming (css)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@layer base, theme; @import 'stream-chat-react/dist/css/v2/index.css' layer(base); @layer theme { .str-chat__theme-custom { --str-chat__primary-color: #009688; --str-chat__active-primary-color: #004d40; --str-chat__surface-color: #f5f5f5; --str-chat__secondary-surface-color: #fafafa; --str-chat__primary-surface-color: #e0f2f1; --str-chat__primary-surface-color-low-emphasis: #edf7f7; --str-chat__border-radius-circle: 6px; } } html, body, #root { height: 100%; } body { margin: 0; } #root { display: flex; } .str-chat__channel-list { width: 30%; } .str-chat__channel { width: 100%; } .str-chat__thread { width: 45%; }

Now that the stylesheet is ready we can pass this theme class name to the theme property of the Chat component so we can see the change in styles:

react-chat+Theming (tsx)
1
2
3
4
5
return ( <Chat client={client} theme='str-chat__theme-custom'> {/* ... */} </Chat> );

You can read more about theming in our documentation.

Customizing Your Own UI Components

While all of the React components in the library render with default markup and styling, you can also pass your own custom UI components via props to adjust the look and feel of your application to meet your design specifications. Most of the components are easily replaceable through Channel props or through props of other core components.

In this example we'll create custom ChannelPreview and Message components.

Note: All custom UI components that override the defaults receive the same props as their default counterparts.

Update src/App.tsx with the following code:

react-chat+Customize (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { ChannelFilters, ChannelSort, User } from 'stream-chat'; import { Chat, Channel, ChannelHeader, ChannelList, ChannelPreviewProps, MessageInput, MessageList, Thread, Window, useMessageContext, useCreateChatClient, } from 'stream-chat-react'; import './layout.css'; const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userName = 'REPLACE_WITH_USER_NAME'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; const sort: ChannelSort = { last_message_at: -1 }; const filters: ChannelFilters = { type: 'messaging', members: { $in: [userId] }, }; const CustomChannelPreview = (props: ChannelPreviewProps) => { const { channel, setActiveChannel } = props; const { messages } = channel.state; const messagePreview = messages[messages.length - 1]?.text?.slice(0, 30); return ( <div onClick={() => setActiveChannel?.(channel)} style={{ margin: '12px', display: 'flex', gap: '5px' }}> <div> <img src={channel.data?.image} alt='channel-image' style={{ height: '36px' }} /> </div> <div style={{ flex: 1 }}> <div>{channel.data?.name || 'Unnamed Channel'}</div> {messagePreview && <div style={{ fontSize: '14px' }}>{messagePreview}</div>} </div> </div> ); }; const CustomMessage = () => { const { message } = useMessageContext(); return ( <div> <b style={{ marginRight: '4px' }}>{message.user?.name}</b> {message.text} </div> ); }; const App = () => { const client = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: user, }); if (!client) return <div>Setting up client & connection...</div>; return ( <Chat client={client}> <ChannelList Preview={CustomChannelPreview} filters={filters} sort={sort} options={options} /> <Channel Message={CustomMessage}> <Window> <ChannelHeader /> <MessageList /> <MessageInput /> </Window> <Thread /> </Channel> </Chat> ); }; export default App;

Create a Custom Message Attachment Type

In this example, you’ll create a custom Attachment component that renders a different UI if the attachment is of type product.

To see this functionality in action, you’ll:

  1. Set up your app to automatically send a message with a custom attachment on mount.
  2. Make a query to fetch the created channel after the user connects.
  3. Send a message with the product attachment included. The custom Attachment component will then render in the MessageList.

Update src/App.tsx with the following code:

react-chat+Custom+Message+&+Attachments (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { useEffect } from 'react'; import { ChannelFilters, ChannelSort, User } from 'stream-chat'; import { Attachment, Chat, Channel, ChannelHeader, ChannelList, MessageInput, MessageList, Thread, Window, useCreateChatClient, type AttachmentProps, } from 'stream-chat-react'; import './layout.css'; const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userName = 'REPLACE_WITH_USER_NAME'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; const sort: ChannelSort = { last_message_at: -1 }; const filters: ChannelFilters = { type: 'messaging', members: { $in: [userId] }, }; export type CustomAttachmentType = { image: string; name: string; type: string; url: string; }; const attachments: CustomAttachmentType[] = [ { image: 'https://images-na.ssl-images-amazon.com/images/I/71k0cry-ceL._SL1500_.jpg', name: 'iPhone', type: 'product', url: 'https://goo.gl/ppFmcR', }, ]; const CustomAttachment = (props: AttachmentProps) => { const { attachments } = props; const [attachment] = (attachments || []) as CustomAttachmentType[]; if (attachment?.type === 'product') { return ( <div> Product: <a href={attachment.url} rel="noreferrer"> <img alt="custom-attachment" height="100px" src={attachment.image} /> <br /> {attachment.name} </a> </div> ); } return <Attachment {...props} />; }; const App = () => { const client = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: user, }); useEffect(() => { if (!client) return; const initAttachmentMessage = async () => { const [channel] = await client.queryChannels(filters, sort); await channel.sendMessage({ text: 'Your selected product is out of stock, would you like to select one of these alternatives?', attachments, }); }; initAttachmentMessage().catch((error) => { console.error(`Failed to initialize attachments`, error); }); }, [client]); if (!client) return <div>Setting up client & connection...</div>; return ( <Chat client={client}> <ChannelList filters={filters} sort={sort} /> <Channel Attachment={CustomAttachment}> <Window> <ChannelHeader /> <MessageList /> <MessageInput /> </Window> <Thread /> </Channel> </Chat> ); }; export default App;

For more information read the React SDK Overview documentation or if you want to build a more complex chat application, review Stream’s API documentation.

Enable EmojiPicker and Emoji Autocomplete

No chat experience is complete without emojis - we've made it easy for you to extend your MessageInput component with EmojiPicker component and emoji autocomplete (through the use of SearchIndex).

For this part we'll need emoji-mart related packages on top of which our SDK components are built (make sure versions of these packages fit within our peer dependency requirements), to install these run:

shell
1
npm i emoji-mart @emoji-mart/react @emoji-mart/data

And now the actual code:

react-chat+Emojis (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import { ChannelFilters, ChannelOptions, ChannelSort, User } from 'stream-chat'; import { Chat, Channel, ChannelHeader, ChannelList, MessageInput, MessageList, Thread, Window, useCreateChatClient, } from 'stream-chat-react'; import { EmojiPicker } from 'stream-chat-react/emojis'; import { init, SearchIndex } from 'emoji-mart'; import data from '@emoji-mart/data'; import './layout.css'; const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userName = 'REPLACE_WITH_USER_NAME'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; const sort: ChannelSort = { last_message_at: -1 }; const filters: ChannelFilters = { type: 'messaging', members: { $in: [userId] }, }; init({ data }); const App = () => { const client = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: user, }); if (!client) return <div>Setting up client & connection...</div>; return ( <Chat client={client}> <ChannelList filters={filters} sort={sort} /> <Channel EmojiPicker={EmojiPicker} emojiSearchIndex={SearchIndex}> <Window> <ChannelHeader /> <MessageList /> <MessageInput /> </Window> <Thread /> </Channel> </Chat> ); }; export default App;

Create a Livestream Style Chat App

For the next example, you’ll customize the original code snippet to work well for a livestream style chat application. In livestream apps, the user interface tends to be more compact and message seen/read states can get noisy as volume increases.

For this reason, we've changed the channel type and switched the message list to use VirtualizedMessageList component, which handles list virtualization out-of-the-box and manages memory build-up.

Update src/App.tsx with the following code to see a simple livestream example:

react-chat+Live+Stream+Chat (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { useEffect, useState } from 'react'; import { Channel as StreamChannel, User } from 'stream-chat'; import { Channel, ChannelHeader, Chat, MessageInput, VirtualizedMessageList, Window, useCreateChatClient, } from 'stream-chat-react'; import './layout.css'; const apiKey = 'REPLACE_WITH_API_KEY'; const userId = 'REPLACE_WITH_USER_ID'; const userName = 'REPLACE_WITH_USER_NAME'; const userToken = 'REPLACE_WITH_USER_TOKEN'; const user: User = { id: userId, name: userName, image: `https://getstream.io/random_png/?name=${userName}`, }; const App = () => { const [channel, setChannel] = useState<StreamChannel>(); const client = useCreateChatClient({ apiKey, tokenOrProvider: userToken, userData: user, }); useEffect(() => { if (!chatClient) return; const spaceChannel = chatClient.channel('livestream', 'spacex', { image: 'https://goo.gl/Zefkbx', name: 'SpaceX launch discussion', }); setChannel(spaceChannel); }, [chatClient]); if (!client) return <div>Setting up client & connection...</div>; return ( <Chat client={client} theme='str-chat__theme-dark'> <Channel channel={channel}> <Window> <ChannelHeader live /> <VirtualizedMessageList /> <MessageInput focus /> </Window> </Channel> </Chat> ); }; export default App;

There are a few important differences compared to the first example:

  • You're using the livestream channel type, which disables typing events and seen/read states.
  • You set the theme to str-chat__theme-dark, which enables dark mode for the livestream channel type.
  • You're using the VirtualizedMessageList component, which supports the high message volume unique to livestream events.

As a next step, check out the Stream React examples source code for the chat demos on Stream’s website. Here, you can view more detailed integrations and complex use-cases involving Stream's React components.

If you got stuck at any point during this tutorial make sure to take a look at sandbox bellow which includes changes mentioned in this tutorial.

Final Thoughts

In this chat app tutorial we built a fully functioning React messaging app with our React SDK component library. We also showed how easy it is to customize the behavior and the style of the React chat app components with minimal code changes.

Both the chat SDK for React and the API have plenty more features available to support more advanced use-cases such as push notifications, content moderation, rich messages and more. Please check out our React Native tutorial too. If you want some inspiration for your app, download our free chat interface UI kit.

Give us feedback!

Did you find this tutorial helpful in getting you up and running with your project? Either good or bad, we're looking for your honest feedback so we can improve.

Start coding for free

No credit card required.
If you're interested in a custom plan or have any questions, please contact us.