Socket Listener
Introduction
Listener
is a class that creates a template for receiving events from server. Its strength is its strict and
predictable data structure which together with typescript gives a lot of confidence while developing data exchange
features.
To use the listener we need to trigger the listen
method with provided callback.
Purpose
- Configures events listeners templates
- Standardizes the system’s data schema
- Listen to server events
Initialization
Listener should be initialized from the Socket instance with createListener
method. This passes a shared reference to
the place that manages communication in the application.
import { socket } from "./socket";
export const onChatMessage = socket.createListener<ChatMessageType>()({
endpoint: "chat-message", // endpoint of the event
});
export const onUserStatusChange = socket.createListener<UserStatusType>()({
endpoint: "status", // endpoint of the event
});
Usage
Listening
Use the listen
method to start the listening for incoming events.
onChatMessage.listen({
callback: (message) => {
// message have type ChatMessageType
console.log(message);
},
});
Remove listener
Use the returned method to stop listening.
const removeEvent = onChatMessage.listen({ callback: (message) => console.log(message) });
...
removeEvent() // We remove listener
Remove event listeners directly from the adapter
const callback = (message) => console.log(message);
const removeEvent = onChatMessage.listen({ callback });
...
socket.adapter.listeners.delete(onChatMessage.endpoint) // We remove ALL listeners from given event
socket.adapter.listeners.get(onChatMessage.endpoint).delete(callback) // We remove single listener from given event
Listen to event once
const removeEvent = onChatMessage.listen({
callback: (message) => {
console.log(message);
removeEvent(); // We listen only to single event
},
});
Listening with onData hook
You can use onData
method to listen to all events of given listener. This method is useful when you want to attach
some logic to any occurrence of the event. It's additional way of hooking into the listener groups.
export const onNoteChange = socket
.createListener<NoteData>()({
endpoint: "notes/:id",
})
.onData((event) => {
// We will receive all events from given listener,
// no mater of ID, as it will be filled later on while listening
console.log(event);
});
onNoteChange.setParams({ noteId: 1 }).listen(() => {
// some logic
});
onNoteChange.setParams({ noteId: 2 }).listen(() => {
// some logic
});
// console.log(event) ===> { noteId: 1, ... }
// console.log(event) ===> { noteId: 2, ... }
Integration with Hyper Fetch
We can use this to make simple integration with Hyper Fetch. We can use onData
hook to update the cache with new data
from the socket.
// ==> Our core setup
const client = new Client({ url: "http://localhost:3000" });
const getNote = client.createRequest()({
endpoint: "/note/:noteId";
})
// ==> Our realtime listener
export const onNoteChange = socket.createListener<NoteData>()({
endpoint: "notes",
}).onData((event) => {
const { noteId } = event;
// Fill the params so our cache can find the request in storage
const noteRequest = client.cache.get(getNote.setParams({ noteId }));
// Update the cache
client.cache.update(noteRequest, (prevData) => {
return {...prevData, data: event}
});
});
Methods
Using methods on a listener is different from other classes in Hyper Fetch. This is to ensure isolation between different uses, which allows you to avoid overwriting previously-prepared listeners and to dynamically generate keys for queues or the cache.
Using any method on listener returns its clone! We don't return a reference!
// ❌ WRONG
const listener = getChatMessageListener;
listener.setOptions({ ... }); // Returns CLONED listener with assigned options
listener.listen(); // Error - no options will be applied
// ✅ Good
const listener = getChatMessageListener;
const listenerWithOptions = listener.setOptions({ ... }); // Returns CLONED listener with assigned options
listenerWithOptions.listen(); // Success - options applied
Parameters
Configuration options
{
endpoint: Endpoint;
options: T extends SocketAdapterType<any, any, infer O, any> ? O : never;
params: string extends T ? NegativeTypes : (T extends ${string}:,${infer Param}/,${infer Rest} ? [k in Param | keyof ExtractRouteParams<Rest>]: ParamType : (T extends ${string}:,${infer Param} ? [k in Param]: ParamType : NegativeTypes));
}