Skip to main content
Version: v7.0.0

Listener

Read the API Reference »

The Listener class is a key component of Hyper Fetch's real-time communication capabilities. It provides a powerful, type-safe way to define, configure, and manage event listeners for data coming from a server through sockets. By encapsulating all the details needed to listen for an event—such as the topic and data structure—Listener ensures consistency, predictability, and flexibility in how your application handles real-time updates.

With a strict and predictable data structure, Listener makes it easy to build features that react to live events with confidence. Its design, especially when paired with TypeScript, helps you create robust, maintainable, and interactive applications by ensuring that the data you receive from the server matches the types you expect.


Purpose
  1. To create consistent and reusable event listener templates.
  2. To standardize the data schema for incoming real-time events.
  3. To provide a consistent API for listening to events, regardless of the underlying adapter.
  4. To hold instructions on how to connect to specific event topics.
  5. To ensure that incoming event data is strongly-typed and structured, making it safe and easy to work with.

Initialization

A Listener should be initialized from a Socket instance using the createListener method. This approach ensures that the listener shares a common communication manager with the rest of your application, promoting a unified architecture for handling real-time data.

import { socket } from "./socket";

export const onChatMessage = socket.createListener<ChatMessageType>()({
topic: "chat-message",
});

export const onUserStatusChange = socket.createListener<UserStatusType>()({
topic: "status",
});

Usage

Listening for Events

To begin receiving events, use the listen method and provide a callback function. This function will be executed each time a new event is received on the specified topic. The data passed to the callback will be automatically typed based on the generic you provided during initialization.

onChatMessage.listen({
callback: (message) => {
// `message` is automatically typed as ChatMessageType
console.log(message);
},
});

Stopping a Listener

The listen method returns a function that, when called, will remove that specific listener. This is the standard way to clean up and stop listening for events, for example, when a component unmounts.

// The listen method returns a function to remove the listener
const removeListener = onChatMessage.listen({ callback: (message) => console.log(message) });

// ...sometime later...

// Call the returned function to stop listening
removeListener();

You can also interact with the underlying adapter to remove listeners, which can be useful for more advanced scenarios, such as removing all listeners for a given event topic.

const callback = ({ data }) => console.log(data);
const unmountListener = onChatMessage.listen({ callback });

// ...

// Remove ALL listeners for the 'chat-message' topic
socket.adapter.listeners.delete(onChatMessage.topic);

// Remove a *single* specific listener from the 'chat-message' topic
socket.adapter.listeners.get(onChatMessage.topic)?.delete(callback);

Listen Only Once

If you only need to handle the next event that comes in, you can simply call the removeListener function from within your callback.

const removeEvent = onChatMessage.listen({
callback: (message) => {
console.log(message);
removeEvent(); // Stop listening after the first event
},
});

Features

Here are some of the features of the Listener class.


Dynamic Topics

Listeners support dynamic topics with parameters, similar to requests. This allows you to create reusable listener templates for resources that require an identifier, such as a specific chat room or user.

Define the parameter in the topic string with a colon prefix (e.g., :id).

export const onNoteChange = socket.createListener<NoteData>()({
topic: "notes/:noteId",
});

Then, use the setParams method to provide a value for the parameter before you start listening. Each listener with a different parameter will be treated as a separate event stream.

// Listen to events for note with ID 1
onNoteChange.setParams({ noteId: 1 }).listen(/* ... */);

// Listen to events for note with ID 2
onNoteChange.setParams({ noteId: 2 }).listen(/* ... */);

Global Data Hooks

You can use the onData method to create a "global" hook for a listener template. This callback will be triggered for every event that matches the listener's topic, even when dynamic parameters are used. It's an excellent way to centralize logic that needs to react to any event of a certain type, regardless of its specific parameters.

export const onNoteChange = socket
.createListener<NoteData>()({
topic: "notes/:noteId",
})
.onData((event) => {
// This will be called for any note change, regardless of the 'noteId'
console.log("A note changed:", event.data);
});

onNoteChange.setParams({ noteId: 1 }).listen(/* ... */);
onNoteChange.setParams({ noteId: 2 }).listen(/* ... */);

// The onData callback will fire for events on both `notes/1` and `notes/2`

Integration with Hyper Fetch

A powerful use case for the onData hook is to create a seamless integration with Hyper Fetch's caching layer. When you receive a real-time update, you can use it to instantly update the relevant cached data from a Request. This keeps your application's state synchronized without needing to re-fetch data from the server.

Here's an example of updating a cached note when an update event is received via a socket.

// 1. Your core client and request setup
const client = new Client({ url: "http://localhost:3000" });
const getNote = client.createRequest()({
topic: "/notes/:noteId",
});

// 2. Your realtime listener with a data hook
export const onNoteChange = socket
.createListener<NoteData>()({
topic: "notes/:noteId",
})
.onData((event) => {
const { noteId } = event.data;

// 3. Create a request instance that matches the cache entry
const noteRequest = getNote.setParams({ noteId });

// 4. Update the cache with the new data from the socket event
client.cache.update(noteRequest, (prevData) => {
return { ...prevData, data: event.data };
});
});

Methods

The Listener class offers a suite of methods to configure and interact with a listener instance. You can find a comprehensive list of these methods and their detailed descriptions in the API reference.

Methods
Name Description
setParams(params)
setOptions(options)
clone(options)
listen

Detailed Listener API Methods
Explore all available methods, their parameters, and return values for the Listener class.

It is crucial to understand how methods on a Listener instance operate to ensure predictable behavior in your application.

Methods Return Clones

Methods on a Listener instance (e.g., setParams, setOptions) do not modify the original listener instance in place. Instead, they return a new, cloned instance of the listener with the specified modifications applied. The original listener instance remains unchanged.

Always use the returned new instance for subsequent operations. This immutable approach ensures isolation between different listener configurations and prevents unintended side effects.

// ❌ WRONG
const listener = onChatMessage;

// This returns a *new* listener instance, but it's not being assigned or used.
// The original `listener` variable remains unchanged.
listener.setOptions({ ... });

// This will listen without the options you intended to apply.
listener.listen();
// ✅ CORRECT
const listener = onChatMessage;

// Assign the new, cloned listener with options to a new variable
const listenerWithOptions = listener.setOptions({ ... });

// Use the correctly configured listener
listenerWithOptions.listen();

// ✅ Also correct (method chaining)
onChatMessage
.setOptions({ ... })
.listen();

Configuration

You can provide additional configuration options when creating a listener to customize its behavior. These options are passed in the object to createListener.

// 1. Initialize the socket
const socket = new Socket({
url: "ws://localhost:3000",
});

// 2. Create the listener template
const listener = socket.createListener<{ message: string }>()({
topic: "chat-message",
});

ListenerOptionsType
Name Type Description
options
ExtractAdapterListenerOptionsType<AdapterType>
topic
Topic


TypeScript

The Listener class is designed with TypeScript at its core to provide a superior developer experience and robust type safety for real-time events.

Response Type

When you create a listener, you should provide the type of the data you expect to receive in the event as a generic. This ensures that the data passed to your listen and onData callbacks is fully typed, enabling autocompletion and preventing common errors.

// Define the type for your event data
type ChatMessageType = {
id: string;
author: string;
message: string;
timestamp: number;
};

// Provide it as a generic to createListener
const onChatMessage = socket.createListener<ChatMessageType>()({
topic: "chat-message",
});

onChatMessage.listen(({ data }) => {
// `data` is now strongly-typed as ChatMessageType
console.log(data.message);
console.log(data.author);
console.log(data.nonExistentProperty); // TypeScript Error
});

This simple yet powerful feature eliminates guesswork and ensures that your application correctly handles the data structures sent by the server.


See More

Socket
Learn more about the Socket class and its configuration options.
Emitter
Learn more about the Emitter class and its configuration options.