Important
The ably-js React Hooks are currently in the release candidate phase, and there may be breaking changes in a future non-major release.
Use Ably in your React application using idiomatic, easy to use, React Hooks!
Using this module you can:
- Interact with Ably channels using a React Hook.
- Publish messages via Ably using the channel instances the hooks provide
- Get notifications of user presence on channels
- Send presence updates
The hooks provide a simplified syntax for interacting with Ably, and manage the lifecycle of the Ably SDK instances for you taking care to subscribe and unsubscribe to channels and events when your react components re-render.
Note
For more information what Ably is and concepts you need, please see the official Ably documentation. The official docs also include a more complete React Hooks quickstart guide.
The hooks are compatible with all versions of React above 16.8.0
Start by connecting your app to Ably using the AblyProvider
component. See the ClientOptions
documentation for information about what options are available when creating an Ably client. If you want to use the usePresence
hook, you'll need to explicitly provide a clientId
.
The AblyProvider
should be high in your component tree, wrapping every component which needs to access Ably.
import { AblyProvider } from "ably/react";
import * as Ably from "ably";
const client = new Ably.Realtime({ key: "your-ably-api-key", clientId: 'me' });
root.render(
<AblyProvider client={client}>
<App />
</AblyProvider>
)
Once you've done this, you can use the hooks
in your code. The simplest example is as follows:
const { channel } = useChannel("your-channel-name", (message) => {
console.log(message);
});
Every time a message is sent to your-channel-name
it'll be logged to the console. You can do whatever you need to with those messages.
Our react hooks are designed to run on the client-side, so if you are using server-side rendering, make sure that your components which use Ably react hooks are only rendered on the client side.
The useChannel hook lets you subscribe to an Ably Channel and receive messages from it.
const { channel, ably } = useChannel("your-channel-name", (message) => {
console.log(message);
});
Both the channel instance, and the Ably JavaScript SDK instance are returned from the useChannel call.
useChannel
really shines when combined with a regular react useState
hook - for example, you could keep a list of messages in your app state, and use the useChannel
hook to subscribe to a channel, and update the state when new messages arrive.
const [messages, updateMessages] = useState([]);
const { channel } = useChannel("your-channel-name", (message) => {
updateMessages((prev) => [...prev, message]);
});
// Convert the messages to list items to render in a react component
const messagePreviews = messages.map((msg, index) => <li key={index}>{msg.data.someProperty}</li>);
useChannel
supports all of the parameter combinations of a regular call to channel.subscribe
, so you can filter the messages you subscribe to by providing a message type
to the useChannel
function:
const { channel } = useChannel("your-channel-name", "test-message", (message) => {
console.log(message); // Only logs messages sent using the `test-message` message type
});
The channel
instance returned by useChannel
can be used to send messages to the channel. It's just a regular Ably JavaScript SDK channel
instance.
channel.publish("test-message", { text: "message text" });
Because we're returning the channel instance, and Ably SDK instance from our useChannel
hook, you can subsequently use these to perform any operations you like on the channel.
For example, you could retrieve history like this:
const { channel } = useChannel("your-channel-name", (message) => {
console.log(message);
});
const history = channel.history((err, result) => {
var lastMessage = resultPage.items[0];
console.log('Last message: ' + lastMessage.id + ' - ' + lastMessage.data);
});
We support providing ChannelOptions to the useChannel
hook:
This means you can use features like rewind
:
const { channel } = useChannel({ channelName: "your-channel-name", options: { params: { rewind: '1' } } }, (message) => {
...
});
Subscription filters are also supported:
const deriveOptions = { filter: 'headers.email == `"[email protected]"` || headers.company == `"domain"`' }
const { channel } = useChannel({ channelName: "your-derived-channel-name", options: { ... }, deriveOptions }, (message) => {
...
});
Please note that attempts to publish to a derived channel (the one created or retrieved with a filter expression) will fail. In order to send messages to the channel called "your-derived-channel-name" from the example above, you will need to create another channel instance without a filter expression.
const channelName = "your-derived-channel-name";
const options = { ... };
const deriveOptions = { filter: 'headers.email == `"[email protected]"` || headers.company == `"domain"`' }
const callback = (message) => { ... };
const { channel: readOnlyChannelInstance } = useChannel({ channelName, options, deriveOptions }, callback);
const { channel: readWriteChannelInstance } = useChannel({ channelName, options }, callback); // NB! No 'deriveOptions' passed here
readWriteChannelInstance.publish("test-message", { text: "message text" });
The usePresence hook lets you subscribe to presence events on a channel - this will allow you to get notified when a user joins or leaves the channel. To find out more about Presence, see the presence documentation.
Please note that fetching present members is executed as an effect, so it'll load in after your component renders for the first time.
const { presenceData, updateStatus } = usePresence("your-channel-name");
// Convert presence data to list items to render
const peers = presenceData.map((msg, index) => <li key={index}>{msg.clientId}: {msg.data}</li>);
usePresence
returns an array of presence messages - again each message is a regular Ably JavaScript SDK presenceMessage
instance.
You can optionally provide a string when you usePresence
to set an initial presence data
string.
const { presenceData, updateStatus } = usePresence("your-channel-name", "initial state");
// The `updateStatus` function can be used to update the presence data for the current client
updateStatus("new status");
The new state will be sent to the channel, and any other clients subscribed to the channel will be notified of the change immediately.
If you don't want to use the presenceData
returned from usePresence, you can configure a callback
const { updateStatus } = usePresence("your-channel-name", "initial state", (presenceUpdate) => {
console.log(presenceUpdate);
});
usePresence supports objects, as well as strings
usePresence("your-channel-name", { foo: "bar" });
and if you're using TypeScript
there are type hints to make sure that updates are of the same type
as your initial constraint, or a provided generic type parameter:
const TypedUsePresenceComponent = () => {
// In this example MyPresenceType will be checked - if omitted, the shape of the initial
// value will be used ...and if that's omitted, `any` will be the default.
const { val } = usePresence<MyPresenceType>("testChannelName", { foo: "bar" });
return (
<div role='presence'>
{JSON.stringify(val)}
</div>
);
}
interface MyPresenceType {
foo: string;
}
PresenceData
is a good way to store synchronised, per-client metadata, so types here are especially valuable.
The useConnectionStateListener
hook lets you attach a listener to be notified of connection state changes. This can be useful for detecting when the client has lost connection.
useConnectionStateListener((stateChange) => {
console.log(stateChange.current) // the new connection state
console.log(stateChange.previous) // the previous connection state
console.log(stateChange.reason) // if applicable, an error indicating the reason for the connection state change
})
You can also pass in a filter to only listen to a set of connection states:
useConnectionStateListener('failed', listener); // the listener only gets called when the connection state becomes failed
useConnectionStateListener(['failed', 'suspended'], listener); // the listener only gets called when the connection state becomes failed or suspended
The useChannelStateListener
hook lets you attach a listener to be notified of channel state changes. This can be useful for detecting when a channel error has occured.
useChannelStateListener((stateChange) => {
console.log(stateChange.current) // the new channel state
console.log(stateChange.previous) // the previous channel state
console.log(stateChange.reason) // if applicable, an error indicating the reason for the channel state change
})
You can also pass in a filter to only listen to a set of channel states:
useChannelStateListener('failed', listener); // the listener only gets called when the channel state becomes failed
useChannelStateListener(['failed', 'suspended'], listener); // the listener only gets called when the channel state becomes failed or suspended
The useAbly
hook lets you access the Ably client used by the AblyProvider
context. This can be useful if you need to access ably-js APIs which aren't available through our react-hooks library.
const client = useAbly();
client.authorize();
When using the Ably react hooks, your Ably client may encounter a variety of errors, for example if it doesn't have permissions to attach to a channel it may encounter a channel error, or if it loses connection from the Ably network it may encounter a connection error.
To allow you to handle these errors, the useChannel
and usePresence
hooks return connection and channel errors so that you can react to them in your components:
const { connectionError, channelError } = useChannel('my_channel', messageHandler);
if (connectionError) {
// TODO: handle connection errors
} else if (channelError) {
// TODO: handle channel errors
} else {
return <AblyChannelComponent />
}
Alternatively, you can also pass callbacks to the hooks to be called when the client encounters an error:
useChannel({
channelName: 'my_channel',
onConnectionError: (err) => { /* handle connection error */ },
onChannelError: (err) => { /* handle channel error */ },
}, messageHandler);
If you need to use multiple Ably clients on the same page, the easiest way to do so is to keep your clients in separate AblyProvider
components. However, if you need to nest AblyProvider
s, you can pass a string id for each client as a prop to the provider.
root.render(
<AblyProvider client={client1} id={'providerOne'}>
<AblyProvider client={client2} id={'providerTwo'}>
<App />
</AblyProvider>
</AblyProvider>
)
This id
can then be passed in to each hook to specify which client to use.
const ablyContextId = 'providerOne';
const client = useAbly(ablyContextId);
useChannel({ channelName: "your-channel-name", id: ablyContextId }, (message) => {
console.log(message);
});
usePresence({ channelName: "your-channel-name", id: ablyContextId }, (presenceUpdate) => {
...
})
Currently, when using our react library with NextJS you may encounter some warnings which arise due to some static checks against subdependencies of the library. While these warnings won't affect the performance of the library and are safe to ignore, we understand that they are an annoyance and offer the following advice to prevent them from displaying:
This warning comes from keyv which is a subdependency of our NodeJS http client. You can read more about the reason this warning is displayed at jaredwray/keyv#45.
You can avoid this warning by overriding the version of keyv used by adding the following to your package.json:
"overrides": {
"cacheable-request": {
"keyv": "npm:@keyvhq/core@~1.6.6"
}
}
These warnings come from devDependencies which are conditionally loaded in the ws module (our NodeJS websocket client). They aren't required for the websocket client to work, however NextJS will statically analyse imports and incorrectly assume that these are needed.
You can avoid this warning by adding the following to your next.config.js:
module.exports = {
webpack: (config) => {
config.externals.push({
'utf-8-validate': 'commonjs utf-8-validate',
'bufferutil': 'commonjs bufferutil',
})
return config
},
}