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:
123npm create vite chat-example -- --template react-ts cd chat-example npm i stream-chat stream-chat-react
123yarn create vite chat-example --template react-ts cd chat-example yarn add 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 serversuserId
anduserToken
- authorization information of the current chat useruserName
- 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 theuseChatContext
hook (exported by thestream-chat-react
) to access it.
123456789101112131415161718192021import { 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:
- Chat
- Channel
- ChannelHeader
- MessageInput (with EmojiPicker & emoji autocomplete)
- MessageList
- Thread
- Window
Let's extend our src/App.tsx
code with this new setup:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354import { 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:
123456789101112131415161718192021html, 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:
123// 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 messageoptions
: 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:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061import 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:
123456789101112131415161718192021222324252627282930313233343536@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:
12345return ( <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:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687import { 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:
- Set up your app to automatically send a message with a custom attachment on mount.
- Make a query to fetch the created channel after the user connects.
- Send a message with the product attachment included. The custom
Attachment
component will then render in theMessageList
.
Update src/App.tsx
with the following code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113import { 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:
1npm i emoji-mart @emoji-mart/react @emoji-mart/data
1yarn add emoji-mart @emoji-mart/react @emoji-mart/data
And now the actual code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263import { 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:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960import { 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.