Tutorials
Build a Production-Ready React Chat App in 3 Minutes with One Component
A drop-in ChatRoom component with presence and typing indicators, then what to change before it goes in front of real users.
Chat is one of those features that looks small and is not. Message delivery, ordering, presence, typing indicators, reconnection after a dropped network, history on join.
For a separate operational perspective on how teams organise and measure work, remote employee monitoring software.
For independent background and broader industry context, see Socket.IO.
The component below handles the plumbing. The second half of this article is about the parts a component cannot decide for you, which is where "works in a demo" and "in front of users" diverge.
The three minutes
npm install @vaultrice/react-components
import { ChatRoom } from '@vaultrice/react-components'
export default function Support() {
return (
<ChatRoom
credentials={{
projectId: 'your-project-id',
getAccessToken: async () => {
const res = await fetch('/api/vaultrice-token')
if (!res.ok) throw new Error('Failed to fetch token')
const { accessToken } = await res.json()
return accessToken
}
}}
id="support-room-42"
user={{ name: currentUser.name }}
/>
)
}
That is a working room — messages, presence, typing indicators, and reconnection.
The id is the room. Two clients with the same id are in the same conversation. Two clients with different ids are not.
The token endpoint
Do not skip this to save time.
The SDK also accepts apiKey and apiSecret directly, which is convenient and puts your secret in every browser that loads the page.
getAccessToken() calls your own endpoint, which authenticates the user however your app already does and returns a short-lived token. The SDK refreshes it automatically.
Your endpoint is the place where you decide whether this user may join this room. That decision cannot live in the client, and if you use direct credentials, there is no place for it at all.
What the component does not decide
Who is allowed in
The room id is the address. A client with valid project credentials and the id reaches the room.
Which means access control is your token endpoint's job. Check that this user belongs in this room before issuing a token scoped to it. A support room named support-room-42 is guessable, and that matters.
What happens to message history
Decide deliberately: how long messages persist, whether a new joiner sees what came before, and whether anyone can delete.
This has a legal dimension if the conversation is with a customer — retention, access requests, deletion.
Identity
The user prop is whatever you pass. The component displays it; it does not verify it.
Take the display name from your session on the server side when minting the token, not from client-supplied input, unless anonymous naming is genuinely what you want.
Moderation
Nothing filters content. For public rooms you need a plan — at minimum a way to remove a message and to stop a participant.
Encryption level
The default is not the strongest available. Security levels go up to end-to-end encryption, where the service does not hold readable content.
For anything sensitive, read the security guide and choose deliberately, rather than accepting the default because it worked in testing.
Going beyond the component
When the drop-in is not enough, the same thing is available at the lower level:
import { NonLocalStorage } from '@vaultrice/sdk'
const nls = new NonLocalStorage(credentials, 'support-room-42')
// join with your own presence payload
nls.join({ name: currentUser.name, role: 'agent' })
nls.on('presence:join', (conn) => {
console.log(conn.data.name, 'joined')
})
const connections = await nls.getJoinedConnections()
// messaging
nls.send({ chat: 'Hello' })
nls.on('message', (msg) => {
render(msg.chat)
})
Use this when you need custom rendering, your own message shape, or behaviour the component does not expose. The component is built on it, so there is no capability cliff.
A short checklist before shipping
- [ ] Token endpoint in place, no
apiSecretin client code - [ ] Endpoint checks the user may join this room
- [ ] Room ids not guessable where the contents are private
- [ ] Display name taken from the server session
- [ ] Message retention decided and implemented
- [ ] Security level chosen deliberately
- [ ] Moderation path for public rooms
- [ ] Behaviour on connection loss checked with the network throttled
The short version
One component gets you a working room with presence, typing and reconnection.
Use getAccessToken() — the token endpoint is also where you decide who may join.
The room id is the address, and a guessable id is a public room.
Take the display name from the server, not from the client.
And decide retention and encryption level before shipping, not after someone asks.