React Client API
The Krmx client implementation is written in TypeScript and provides a React provider and hook. The provider and hook together from a React Context (opens in a new tab).
Context lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props. In the case of the Krmx client the provider is where the websocket connection to the Krmx server is made and it makes the information from the Krmx protocol, such as unlinked and linked users, available to it's child components.
Take a look at the TypeScript SDK reference and source code (opens in a new tab) to learn more about the Client API.
If you need help setting up a project with Krmx, then please following the instructions at Getting Started.
Krmx Provider
The client API provides a KrmxProvider
that you can wrap around your components. The child components will be able to use the Krmx context. The provider is where the websocket connection to the Krmx server is made and it makes the information from the Krmx protocol, such as unlinked and linked users, available to it's child components.
Creating the Provider
Wrap your components into the KrmxProvider.
"use client";
import { useState } from 'react';
import { KrmxProvider } from '@krmx/client';
export default function MyApp() {
const [serverUrl] = useState('ws://localhost:8082');
return (
<KrmxProvider serverUrl={serverUrl} >
<MyComponent/>
</KrmxProvider>
);
}
Properties
To create the provider you have to supply one property: the serverUrl
.
Server Url
For the serverUrl
property you have to provide the ws://
or wss://
url of the websocket endpoint where your Krmx server is running.
For example: ws://my-subdomain.example.org:3002/my-game
or ws://localhost:1234
.
Krmx Hook
The provider makes the information from the Krmx protocol, such as unlinked and linked users, available to it's child components. To access this information you can use the useKrmx()
hook.
function MyComponent() {
const { isConnected, reconnect, isLinked, link, rejectionReason, send, leave, unlink, users } = useKrmx();
}
Fields
The hook returns an object with many fields, that expose part of this information.
Is Connected
The isConnected
field of type boolean, indicates whether the Krmx client could successfully setup a websocket to the server.
Keep in mind that being connected to the server does not mean that the connection is linked to a user.
Reconnect
The reconnect
field is a method that can be used to retry setting up a websocket connection to the Krmx server.
The method takes an optional force boolean as its first argument. When invoked without any arguments, the reconnect()
will only be attempted if not already connected to the server. If the force argument is set to true
and the client is already connected to the Krmx server, that connection will be initially dropped.
Is Linked
The isLinked
field of type boolean, indicates whether the websocket connection the client has to the server has been successfully linked to a user. If isLinked
is false, you can use the link
method to link the connection of this client to a user.
function MyComponent() {
const { isLinked, username } = useKrmx();
if (isLinked) {
return <p>Welcome, {username} 👋</p>;
} else {
return <p>You're not linked!</p>;
}
}
Link
The link
field is a method that can be used to link the connection to a user.
The method require the first argument to be set to the username. For example: link('simon')
.
The method optionally takes a second argument with the authentication of your application as a string. This token is send to the server and will be available in the on('authenticate', ...)
listener under the info.auth
field.
Rejection Reason
The rejectionReason
field of type string is set to undefined
, or to a string value that represents the latest reason why linking the connection to a user was not successful. Some examples: 'user is already linked to a connection'
or 'server is full'
.
Send
The send
field is a method that can be used to send message to the server.
function SendDrawMessageComponent() {
const { isLinked, send } = useKrmx();
if (isLinked) {
return <button onClick={() => { send({ type: 'game/draw', payload: 2 }); }}>
Draw 2 cards!
</button>;
}
}
Leave
The leave
field is a method that can be used to have user leave the server.
Unlink
The unlink
field is a method that can be used to have the connection unlink from the server. This will ensure that the connection to the server stays intact and can be used to link to a user again.
Users
The users
field is an object, that indicates the users that are known to the servers and provides information about which are currently linked to a connection.
The object has the following Type Definition { [username: string]: { isLinked: boolean}};
. Example of its structure
{
"simon": { "isLinked": true },
"lisa": { "isLinked": true },
"rik": { "isLinked": false },
}
Example usage of the users field.
function UsersComponent() {
const { isLinked, users } = useKrmx();
if (isLinked) {
return <ul>
{Object.entries(users)
.map(([username, { isLinked }]) => (
<li key={username}>
{username}
{!isLinked && ' (disconnected)'}
</li>)
)}
</ul>;
}
}
Use Messages
The useMessages
field is a method that let's a component subscribe to incoming events from the Krmx server.
As the first argument to this method, you pass an event handler. This event handler will be invoked every time that the Krmx server sends a message to this client. The event handler is invoked with the message as the first parameter.
As the second argument to useMessages
you'll have to provide the reactive dependency array.
function MyFirstComponentThatUsesMessages() {
const { useMessages } = useKrmx();
useMessages((message: { type: string }) => {
console.info('received', message)
}, []);
}
Keep in mind that messages internal to Krmx, such as 'krmx/join' or 'krmx/leave' messages, are NOT emitted to this event handler. To use the Krmx internal user information, you can use the other properties of the
useKrmx
hook.
Reactive values in useMessages
event handler
To avoid errors, you have to pass an empty dependency array (aka []
). See the example below for the explanation of why you have to pass an empty dependency array.
function MyCountComponent() {
const { useMessages } = useKrmx();
const [first, setFirst] = useState(-1);
const [receivedCounters, setReceivedCounters] = useState<number[]>([]);
const messagesRef = useRef<number[]>([]);
// ...
useMessages((message: { type: string, payload?: number }) => {
if (message.type === 'count') {
// ❌ incorrect (setting reactive values directly)
// Due to the nature of websockets, messages might come in while React is (already) re-rendering,
// these messages could then be sent to the out-of-date useMessages callback, which means that
// the reactive values inside that callback might still be pointing to 'old' values. To avoid
// such tricky situations, the callback is enforced to have zero dependencies.
setReceivedCounters([...receivedCounters, message.payload || -1].slice(-10));
// ✅ correct (using setters)
// If you want to change reactive values, you should use the setters of React state with a
// function, These function will have access to the latest reactive value.
setFirst((first) => first === -1 ? (message.payload || 0) : first);
setReceivedCounters((receivedCounters) => [...receivedCounters, message.payload || -1].slice(-10));
// ❌ incorrect (accessing reactive values)
// As the useMessages callback has an empty list of dependencies, it is only created once, during
// the initial render of the component. Inherently, this means that you should not directly
// access reactive values, as these will always have their initial value set.
if (first !== -1) {
console.info('this is never reached!');
}
// ✅ correct (using a ref)
// Using a ref and mutating that directly from the useMessages callback is fine as refs in React
// don't interact with rendering. In this case, setting reactive values directly based on the
// ref is fine, as the ref ensures that messages are not lost.
messagesRef.current.push(message.payload || 0);
setFirst(messagesRef.current[0]);
setReceivedCounters(messagesRef.current.slice(-10));
}
},
/* Usage of an empty dependency array is enforced here,
passing any other value will throw an error */ []);
// ...
return <div>
<h2>Last 10 Messages (started at {first})</h2>
<ul>
{receivedCounters.map((count, index) =>
<li key={index}>
{count}
</li>
)}
</ul>
</div>
}
useMessages
after unlinking
When your component is using useMessages
, ensure that after the Krmx client has re-linked, that the component will reset its state appropriately. For example, the following component keeps track of the number of messages received and properly sets it to 0 any time the Krmx client is unlinked.
function MyComponentThatProperlyCleansUp() {
const { isLinked, useMessages } = useKrmx();
const [ numberOfMessagesReceived, setNumberOfMessagesReceived ] = useState<number>([]);
// Keep track of the number of messages received
useMessages((message) => {
setNumberOfMessagesReceived(numberOfMessagesReceived => numberOfMessagesReceived + 1);
}, []);
// Properly set the number of messages received to 0, any time the Krmx client is unlinked
useEffect(() => {
if (!isLinked) {
setNumberOfMessagesReceived(0);
}
}, [isLinked]);
if (isLinked) {
return <p>Messages received: {numberOfMessagesReceived}</p>;
} else {
return <p>Not linked.</p>;
}
}
TypeScript SDK Reference
Take a look at the TypeScript SDK reference and source code (opens in a new tab) to learn more about the Client API.