IVS Chat Client Messaging SDK: React
& React Native Best Practices
This document describes the most important practices of using the Amazon IVS Chat Messaging
SDK for React and React Native. This information should enable you to build typical chat
functionality inside a React app, and give you the background you need to dive deeper into the
more advanced parts of the IVS Chat Messaging SDK.
Creating a ChatRoom Initializer Hook
The ChatRoom
class contains core chat methods and listeners for managing
connection state and listening for events like message received and message deleted. Here, we
show how to properly store chat instances in a hook.
Implementation
- TypeScript
-
// useChatRoom.ts
import React from 'react';
import { ChatRoom, ChatRoomConfig } from 'amazon-ivs-chat-messaging';
export const useChatRoom = (config: ChatRoomConfig) => {
const [room] = React.useState(() => new ChatRoom(config));
return { room };
};
- JavaScript
-
import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
export const useChatRoom = (config) => {
const [room] = React.useState(() => new ChatRoom(config));
return { room };
};
Note: We don't use the dispatch
method from the setState
hook,
because you can't update configuration parameters on the fly. The SDK creates an instance
once, and it is not possible to update the token provider.
Important: Use the ChatRoom
initializer
hook once to initialize a new chat-room instance.
Example
TypeScript/JavaScript:
// ...
const MyChatScreen = () => {
const userId = 'Mike';
const { room } = useChatRoom({
regionOrUrl: SOCKET_URL,
tokenProvider: () => tokenProvider(ROOM_ID, ['SEND_MESSAGE']),
});
const handleConnect = () => {
room.connect();
};
// ...
};
// ...
Listening for Connection
State
Optionally, you can subscribe to connection-state updates in your chat-room hook.
Implementation
- TypeScript
-
// useChatRoom.ts
import React from 'react';
import { ChatRoom, ChatRoomConfig, ConnectionState } from 'amazon-ivs-chat-messaging';
export const useChatRoom = (config: ChatRoomConfig) => {
const [room] = useState(() => new ChatRoom(config));
const [state, setState] = React.useState<ConnectionState>('disconnected');
React.useEffect(() => {
const unsubscribeOnConnecting = room.addListener('connecting', () => {
setState('connecting');
});
const unsubscribeOnConnected = room.addListener('connect', () => {
setState('connected');
});
const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
setState('disconnected');
});
return () => {
unsubscribeOnConnecting();
unsubscribeOnConnected();
unsubscribeOnDisconnected();
};
}, []);
return { room, state };
};
- JavaScript
-
// useChatRoom.js
import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
export const useChatRoom = (config) => {
const [room] = useState(() => new ChatRoom(config));
const [state, setState] = React.useState('disconnected');
React.useEffect(() => {
const unsubscribeOnConnecting = room.addListener('connecting', () => {
setState('connecting');
});
const unsubscribeOnConnected = room.addListener('connect', () => {
setState('connected');
});
const unsubscribeOnDisconnected = room.addListener('disconnect', () => {
setState('disconnected');
});
return () => {
unsubscribeOnConnecting();
unsubscribeOnConnected();
unsubscribeOnDisconnected();
};
}, []);
return { room, state };
};
ChatRoom Instance Provider
To use the hook in other components (to avoid prop drilling), you can create a chat-room
provider using React context
.
Implementation
- TypeScript
-
// ChatRoomContext.tsx
import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
const ChatRoomContext = React.createContext<ChatRoom | undefined>(undefined);
export const useChatRoomContext = () => {
const context = React.useContext(ChatRoomContext);
if (context === undefined) {
throw new Error('useChatRoomContext must be within ChatRoomProvider');
}
return context;
};
export const ChatRoomProvider = ChatRoomContext.Provider;
- JavaScript
-
// ChatRoomContext.jsx
import React from 'react';
import { ChatRoom } from 'amazon-ivs-chat-messaging';
const ChatRoomContext = React.createContext(undefined);
export const useChatRoomContext = () => {
const context = React.useContext(ChatRoomContext);
if (context === undefined) {
throw new Error('useChatRoomContext must be within ChatRoomProvider');
}
return context;
};
export const ChatRoomProvider = ChatRoomContext.Provider;
Example
After creating ChatRoomProvider
, you can consume your instance with
useChatRoomContext
.
Important: Put the provider in the root level only if
you need access to the context
between the chat screen and the other components
in the middle, to avoid unnecessary re-renders if you are listening for connections.
Otherwise, put the provider as close as possible to the chat screen.
TypeScript/JavaScript:
// AppContainer
const AppContainer = () => {
const { room } = useChatRoom({
regionOrUrl: SOCKET_URL,
tokenProvider: () => tokenProvider(ROOM_ID, ['SEND_MESSAGE']),
});
return (
<ChatRoomProvider value={room}>
<MyChatScreen />
</ChatRoomProvider>
);
};
// MyChatScreen
const MyChatScreen = () => {
const room = useChatRoomContext();
const handleConnect = () => {
room.connect();
};
// ...
};
// ...
Creating a Message Listener
To stay up to date with all incoming messages, you should subscribe to
message
and deleteMessage
events. Here is some code that provides
chat messages for your components.
Important: For performance purposes, we separate
ChatMessageContext
from ChatRoomProvider
, as we may get many
re-renders when the chat-message listener updates its message’s state. Remember to apply
ChatMessageContext
in components where you will use
ChatMessageProvider
.
Implementation
- TypeScript
-
// ChatMessagesContext.tsx
import React from 'react';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useChatRoomContext } from './ChatRoomContext';
const ChatMessagesContext = React.createContext<ChatMessage[] | undefined>(undefined);
export const useChatMessagesContext = () => {
const context = React.useContext(ChatMessagesContext);
if (context === undefined) {
throw new Error('useChatMessagesContext must be within ChatMessagesProvider);
}
return context;
};
export const ChatMessagesProvider = ({ children }: { children: React.ReactNode }) => {
const room = useChatRoomContext();
const [messages, setMessages] = React.useState<ChatMessage[]>([]);
React.useEffect(() => {
const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
setMessages((msgs) => [message, ...msgs]);
});
const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteEvent) => {
setMessages((prev) => prev.filter((message) => message.id !== deleteEvent.messageId));
});
return () => {
unsubscribeOnMessageDeleted();
unsubscribeOnMessageReceived();
};
}, [room]);
return <ChatMessagesContext.Provider value={messages}>{children}</ChatMessagesContext.Provider>;
};
- JavaScript
-
// ChatMessagesContext.jsx
import React from 'react';
import { useChatRoomContext } from './ChatRoomContext';
const ChatMessagesContext = React.createContext(undefined);
export const useChatMessagesContext = () => {
const context = React.useContext(ChatMessagesContext);
if (context === undefined) {
throw new Error('useChatMessagesContext must be within ChatMessagesProvider);
}
return context;
};
export const ChatMessagesProvider = ({ children }) => {
const room = useChatRoomContext();
const [messages, setMessages] = React.useState([]);
React.useEffect(() => {
const unsubscribeOnMessageReceived = room.addListener('message', (message) => {
setMessages((msgs) => [message, ...msgs]);
});
const unsubscribeOnMessageDeleted = room.addListener('messageDelete', (deleteEvent) => {
setMessages((prev) => prev.filter((message) => message.id !== deleteEvent.messageId));
});
return () => {
unsubscribeOnMessageDeleted();
unsubscribeOnMessageReceived();
};
}, [room]);
return <ChatMessagesContext.Provider value={messages}>{children}</ChatMessagesContext.Provider>;
};
Example in React
Important: Remember to wrap your message container with
ChatMessagesProvider
. The Message
row is an example component
that displays the content of a message.
TypeScript/JavaScript:
// your message list component...
import React from 'react';
import { useChatMessagesContext } from './ChatMessagesContext';
const MessageListContainer = () => {
const messages = useChatMessagesContext();
return (
<React.Fragment>
{messages.map((message) => (
<MessageRow message={message} />
))}
</React.Fragment>
);
};
Example in React Native
By default, ChatMessage
contains id
, which is used
automatically as React keys in FlatList
for each row; therefore, you don't need
to pass keyExtractor
.
- TypeScript
-
// MessageListContainer.tsx
import React from 'react';
import { ListRenderItemInfo, FlatList } from 'react-native';
import { ChatMessage } from 'amazon-ivs-chat-messaging';
import { useChatMessagesContext } from './ChatMessagesContext';
const MessageListContainer = () => {
const messages = useChatMessagesContext();
const renderItem = useCallback(({ item }: ListRenderItemInfo<ChatMessage>) => <MessageRow />, []);
return <FlatList data={messages} renderItem={renderItem} />;
};
- JavaScript
-
// MessageListContainer.jsx
import React from 'react';
import { FlatList } from 'react-native';
import { useChatMessagesContext } from './ChatMessagesContext';
const MessageListContainer = () => {
const messages = useChatMessagesContext();
const renderItem = useCallback(({ item }) => <MessageRow />, []);
return <FlatList data={messages} renderItem={renderItem} />;
};
Multiple Chat Room Instances in an App
If you use multiple concurrent chat rooms in your app, we propose creating each provider
for each chat and consuming it in the chat provider. In this example, we're creating a Help
Bot and Customer Help chat. We create a provider for both.
- TypeScript
-
// SupportChatProvider.tsx
import React from 'react';
import { SUPPORT_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';
export const SupportChatProvider = ({ children }: { children: React.ReactNode }) => {
const { room } = useChatRoom({
regionOrUrl: SOCKET_URL,
tokenProvider: () => tokenProvider(SUPPORT_ROOM_ID, ['SEND_MESSAGE']),
});
return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};
// SalesChatProvider.tsx
import React from 'react';
import { SALES_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';
export const SalesChatProvider = ({ children }: { children: React.ReactNode }) => {
const { room } = useChatRoom({
regionOrUrl: SOCKET_URL,
tokenProvider: () => tokenProvider(SALES_ROOM_ID, ['SEND_MESSAGE']),
});
return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};
- JavaScript
-
// SupportChatProvider.jsx
import React from 'react';
import { SUPPORT_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';
export const SupportChatProvider = ({ children }) => {
const { room } = useChatRoom({
regionOrUrl: SOCKET_URL,
tokenProvider: () => tokenProvider(SUPPORT_ROOM_ID, ['SEND_MESSAGE']),
});
return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};
// SalesChatProvider.jsx
import React from 'react';
import { SALES_ROOM_ID, SOCKET_URL } from '../../config';
import { tokenProvider } from '../tokenProvider';
import { ChatRoomProvider } from './ChatRoomContext';
import { useChatRoom } from './useChatRoom';
export const SalesChatProvider = ({ children }) => {
const { room } = useChatRoom({
regionOrUrl: SOCKET_URL,
tokenProvider: () => tokenProvider(SALES_ROOM_ID, ['SEND_MESSAGE']),
});
return <ChatRoomProvider value={room}>{children}</ChatRoomProvider>;
};
Example in React
Now you can use different chat providers that use the same
ChatRoomProvider
. Later on, you can reuse the same
useChatRoomContext
inside each screen/view.
TypeScript/JavaScript:
// App.tsx / App.jsx
const App = () => {
return (
<Routes>
<Route
element={
<SupportChatProvider>
<SupportChatScreen />
</SupportChatProvider>
}
/>
<Route
element={
<SalesChatProvider>
<SalesChatScreen />
</SalesChatProvider>
}
/>
</Routes>
);
};
Example in React
Native
TypeScript/JavaScript:
// App.tsx / App.jsx
const App = () => {
return (
<Stack.Navigator>
<Stack.Screen name="SupportChat">
<SupportChatProvider>
<SupportChatScreen />
</SupportChatProvider>
</Stack.Screen>
<Stack.Screen name="SalesChat">
<SalesChatProvider>
<SalesChatScreen />
</SalesChatProvider>
</Stack.Screen>
</Stack.Navigator>
);
};
TypeScript/JavaScript:
// SupportChatScreen.tsx / SupportChatScreen.jsx
// ...
const SupportChatScreen = () => {
const room = useChatRoomContext();
const handleConnect = () => {
room.connect();
};
return (
<>
<Button title="Connect" onPress={handleConnect} />
<MessageListContainer />
</>
);
};
// SalesChatScreen.tsx / SalesChatScreen.jsx
// ...
const SalesChatScreen = () => {
const room = useChatRoomContext();
const handleConnect = () => {
room.connect();
};
return (
<>
<Button title="Connect" onPress={handleConnect} />
<MessageListContainer />
</>
);
};