Getting Started with Kuzzle and React #
This tutorial explains how to use Kuzzle with the Javascript SDK 7 and React.
You are going to write a realtime chat: messages are stored as documents in Kuzzle, and every client is kept up to date through document notifications.
To follow this tutorial, you must have a Kuzzle Server up and running. Follow these instructions if this is not already the case: Running Kuzzle.
Having trouble? Get in touch with us on Discord!
Requirements #
- Node.js >= 20 (download page)
- a running Kuzzle V2 stack (instructions here)
Prepare your environment #
Create a React application with Vite and install the Javascript SDK:
npm create vite@latest kuzzle-playground -- --template react
cd kuzzle-playground
npm install
npm install kuzzle-sdk@7You can now empty src/App.jsx and src/App.css: we are going to rewrite them.
Instantiating the SDK #
The SDK client holds the network connection, so the whole application must share a single instance.
Create a src/services/kuzzle.js file:
import { Kuzzle, WebSocket } from "kuzzle-sdk";
// Replace 'localhost' with the hostname of your Kuzzle server
const kuzzle = new Kuzzle(new WebSocket("localhost"));
kuzzle.on("networkError", (error) => {
console.error("Network Error:", error);
});
export default kuzzle;Replace localhost with the hostname of the machine running your Kuzzle server.
Connecting and loading the messages #
Everything that talks to Kuzzle lives in a single custom hook. Create a src/useChat.js file:
import { useCallback, useEffect, useRef, useState } from "react";
import kuzzle from "./services/kuzzle";
// Turns a Kuzzle document into the shape our components expect
const toMessage = (document) => ({
_id: document._id,
value: document._source.value,
username: document._source.username,
createdAt: document._source._kuzzle_info.createdAt,
});
export function useChat(username) {
const [messages, setMessages] = useState([]);
const [ready, setReady] = useState(false);
const roomId = useRef(null);
useEffect(() => {
// Nothing to do until the user has picked a nickname
if (!username) {
return;
}
let cancelled = false;
const start = async () => {
await kuzzle.connect();
// Creates the index and the collection on first run
if (!(await kuzzle.index.exists("chat"))) {
await kuzzle.index.create("chat");
await kuzzle.collection.create("chat", "messages");
}
// Receives a notification for every new message
roomId.current = await kuzzle.realtime.subscribe(
"chat",
"messages",
{},
(notification) => {
if (notification.type !== "document") {
return;
}
if (notification.action !== "create") {
return;
}
setMessages((previous) => [
toMessage(notification.result),
...previous,
]);
},
);
// Loads the hundred most recent messages
const results = await kuzzle.document.search(
"chat",
"messages",
{ sort: { "_kuzzle_info.createdAt": "desc" } },
{ size: 100 },
);
if (cancelled) {
return;
}
setMessages(results.hits.map(toMessage));
setReady(true);
};
start().catch((error) => console.error(error.message));
return () => {
cancelled = true;
if (roomId.current) {
kuzzle.realtime.unsubscribe(roomId.current);
roomId.current = null;
}
};
}, [username]);
const sendMessage = useCallback(
async (value) => {
if (!value) {
return;
}
await kuzzle.document.create("chat", "messages", { value, username });
},
[username],
);
return { messages, ready, sendMessage };
}This hook does the following, once the user has chosen a nickname:
- connects the SDK to Kuzzle,
- creates the
chatindex and themessagescollection if they don't exist yet, - subscribes to the collection, so that every message created by any client is prepended to the local state,
- searches for the hundred most recent messages to fill the history,
- exposes a
sendMessagefunction that creates a new document.
The subscription is opened before the history is fetched, and the cleanup function unsubscribes when the component unmounts. This way no message can slip through between the two calls, and React's Strict Mode does not leave a dangling room behind.
Note that sendMessage does not touch the local state: the new message comes back through the realtime notification, exactly like the ones sent by the other clients.
Displaying the messages #
Create a src/Message.jsx component to render a single message:
export default function Message({ message, username }) {
const origin = message.username === username ? "fromMe" : "fromOthers";
return (
<div className={`${origin} messages`}>
<span>
User: <b>{message.username}</b>
</span>
<span> ({new Date(message.createdAt).toLocaleString()})</span>
<p>{message.value}</p>
</div>
);
}Then add the styles in src/App.css:
.wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
}
.messages {
padding: 10px;
margin: 10px;
width: 45vw;
border-radius: 10px;
}
.fromMe {
text-align: right;
float: right;
margin-left: 49vw;
background-color: rgb(0 40 53 / 30%);
}
.fromOthers {
text-align: left;
float: left;
margin-right: 49vw;
color: #eeefff;
background-color: rgb(0 40 53 / 90%);
}Putting it together #
Finally, rewrite src/App.jsx. It asks for a nickname, then displays the message list and the input used to send new ones:
import { useState } from "react";
import Message from "./Message";
import { useChat } from "./useChat";
import "./App.css";
export default function App() {
const [username, setUsername] = useState(null);
const [draft, setDraft] = useState("");
const { messages, ready, sendMessage } = useChat(username);
// Ask for a nickname before joining the chat
if (!username) {
return (
<form
className="wrapper"
onSubmit={(event) => {
event.preventDefault();
setUsername(event.target.elements.username.value.trim() || null);
}}
>
<input autoFocus name="username" placeholder="Enter your nickname" />
<button type="submit">Join</button>
</form>
);
}
return (
<div>
<form
className="wrapper"
onSubmit={async (event) => {
event.preventDefault();
await sendMessage(draft.trim());
setDraft("");
}}
>
<input
autoFocus
disabled={!ready}
onChange={(event) => setDraft(event.target.value)}
placeholder="Enter your message"
value={draft}
/>
<button disabled={!ready} type="submit">
Send
</button>
</form>
<div>
{messages.map((message) => (
<Message key={message._id} message={message} username={username} />
))}
</div>
</div>
);
}Launch the application:
npm run devOpen the printed URL in two different browser tabs, pick a different nickname in each one, and send a message: it shows up in both tabs instantly, pushed by Kuzzle.
Going further #
Now that you are more familiar with Kuzzle, dive even deeper to learn how to leverage its full capabilities:
- Follow the React with Redux tutorial to move this state into a Redux store
- Discover what this SDK has to offer by browsing other sections of this documentation
- Learn more about Kuzzle realtime engine
- Learn how to use the Kuzzle Admin Console to manage your users and data
- Learn how to use Koncorde to create incredibly fine-grained and blazing-fast subscriptions
- Follow our guide to learn how to manage users, and how to set up fine-grained access control