Skip to main content
Version: v8.0.0

useListener

Read the API Reference »

The useListener hook is your main tool for listening to real-time events from a server in React components. It hooks into the Socket adapter and starts listening for a given event when a component is mounted.

Purpose
  1. Real-time event handling: Listen to server-sent events without manual setup.
  2. Automatic lifecycle management: Manages socket connections when the component mounts and unmounts.
  3. Reactive data updates: Provides the latest data from the server as it arrives.
  4. Connection state tracking: Exposes connected and connecting states to build responsive UIs.
  5. Helper event hooks: Offers onEvent, onError, and onReconnecting for handling specific socket events.

If you intend to send events to the server, we recommend choosing the useEmitter hook.


Quick Start​

To use useListener, provide a Listener instance. The hook automatically listens for events and returns the latest data, timestamp, and connection status.

First, create a Listener instance using your socket client.

Creating a listener
import { socketInstance } from "./socket";

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

Now, use it in your component to listen for new messages. The onEvent helper hook lets you react to incoming data and update your component's state.

Listening to chat messages
import { useListener } from "@hyper-fetch/react";
import { onChatMessage } from "./api/socket";

function App() {
const [messages, setMessages] = useState([]);
const { data, connected, connecting, onEvent, onError, onReconnecting } = useListener(onChatMessage);

onEvent(({ data: message }) => {
if (message) {
setMessages((prev) => [...prev, message]);
}
});

if (!connected && connecting) {
return <CircularProgress />;
}

return (
<Card>
<div className="font-bold mb-2">Chat Room</div>
<div className="flex flex-col gap-2 p-4 min-h-[200px] bg-gray-50/50 dark:bg-gray-900/50 rounded-lg">
{!messages.length && <div className="text-center text-gray-500">No messages yet.</div>}
{messages.map((message, i) => (
<div key={i} className="bg-white dark:bg-gray-800 p-2 rounded-md shadow-sm">
{message.text}
</div>
))}
</div>
</Card>
);
}

Event Handlers​

useListener provides a set of event handler hooks to manage the socket lifecycle and react to events without cluttering your component's rendering logic.

  1. onEvent: Fires whenever a new event is received from the server.
  2. onError: Fires when a connection or subscription error occurs.
  3. onConnected: Fires when the socket connection is successfully opened.
  4. onDisconnected: Fires when the socket connection is closed.
  5. onConnecting: Fires when the socket is connecting.
  6. onReconnecting: Fires when the socket is attempting to reconnect.
  7. onReconnectingFailed: Fires when the socket fails to reconnect.
Using event handlers
import { useListener } from "@hyper-fetch/react";
import { onChatMessage } from "./api/socket";

function App() {
const [messages, setMessages] = useState([]);
const { onEvent, onError, onConnected, onDisconnected, onConnecting, onReconnecting, onReconnectingFailed } =
useListener(onChatMessage);

onEvent(({ data: message }) => {
toast({ type: "success", message: `New message: ${message.text}` });
setMessages((prev) => [...prev, message]);
});

onError((error) => {
toast({ type: "error", message: `Connection error: ${error.message}` });
});

onConnected(() => {
toast({ type: "info", message: "Socket connection opened." });
});

onDisconnected(() => {
toast({ type: "warning", message: "Socket connection closed." });
});

onConnecting(() => {
toast({ type: "info", message: "Socket is connecting." });
});

onReconnecting(({ attempts }) => {
toast({ type: "warning", message: `Reconnecting... (attempt ${attempts})` });
});

onReconnectingFailed(({ attempts }) => {
toast({ type: "error", message: `Reconnecting failed after ${attempts} attempts.` });
});

return (
<Card>
<div className="font-bold mb-2">Chat Room</div>
<div className="flex flex-col gap-2 p-4 min-h-[200px] bg-gray-50/50 dark:bg-gray-900/50 rounded-lg">
{messages.map((message, i) => (
<div key={i} className="bg-white dark:bg-gray-800 p-2 rounded-md shadow-sm">
{message.text}
</div>
))}
</div>
</Card>
);
}

Throttling & Batching​

Every delivered message is a state update, so a fast stream means fast re-rendering. Instead of throttling inside the component, configure a delivery strategy on the listener. useListener needs no options for this - it simply receives fewer, better-shaped updates, and data follows the delivered type.

Sampling - one render per frame at most
export const onPrice = socket.createListener<Price>()({
topic: "prices",
delivery: { strategy: "latest", interval: 16 },
});

function PriceTicker() {
const { data } = useListener(onPrice); // data: Price | null
return <span>{data?.value}</span>;
}
Batching - arrays, one state update per window
export const onTrade = socket.createListener<Trade>()({
topic: "trades",
delivery: { strategy: "batch", interval: 100 },
});

function TradeLog() {
const [trades, setTrades] = useState<Trade[]>([]);
const { onEvent } = useListener(onTrade); // data: Trade[] | null

onEvent(({ data }) => {
setTrades((previous) => [...previous, ...data]);
});
// ...
}

To let the user change the rate, memoize a setDelivery clone - the hook re-subscribes when the delivery changes:

const live = useMemo(() => onPrice.setDelivery({ strategy: "latest", interval: rate }), [rate]);
const { data } = useListener(live);
Custom strategies

Preset objects are compared by value and can be written inline. Custom strategy functions are compared by identity - define them at module level, or the hook will re-subscribe on every render. See the React Throttling & Batching guide.


Selecting Data​

When many components listen to the same topic but need only a part of each message, use the select option. The data value updates only when the selected value changes - messages that don't affect it don't re-render the component. The selected result is structurally shared, so an equal object recreated by the selector keeps its previous reference.

Subscribing to a single symbol
const useSymbolPrice = (id: string) => {
return useListener(onPrices, { select: (prices) => prices[id]?.price });
};

function Price({ id }: { id: string }) {
// Re-renders only when this symbol's price changes
const { data: price } = useSymbolPrice(id);
return <span>{price}</span>;
}
  • onEvent still receives the full message.
  • Without select, every message updates data, even when it is equal to the previous one - messages are events.
  • The selector can close over props - when it changes (e.g. a new id), it is applied to the last message right away.
  • An error thrown by the selector is passed to the closest Error Boundary.
  • Read only the fields you render - the hook re-renders only for the accessed values (dependencyTracking, enabled by default), so reading timestamp or extra makes the component re-render on every message again.

Options​

Configuration options for useListener must be provided as the second parameter.

const { ... } = useListener(listener, {
disabled: false,
// ... and more
});

Below is a full list of available options.

UseListenerOptionsType
Name Type Description
dependencyTracking
boolean
When true (default), only re-renders when accessed state properties change
structuralSharing
boolean
Keep references of the unchanged parts of the selected data between messages (default true). Applies to the select result - without a selector every message updates the data.
select
(data: DataType) => TSelected
Pick or transform the part of the message data used by the component. With a selector the data value updates only when the selected value changes, so components reading data rerender only for the relevant messages.

UseListenerOptionsType API Reference
Learn more about the useListener hook options.

State and Methods​

The useListener hook returns an object containing the socket's state and helper methods.

const values = useListener(listener);

useListener
Name Type Description
connected
boolean
connecting
boolean
data
null | TSelected
extra
null | ExtractSocketExtraType<ListenerType[socket]>
timestamp
null | number
clearState
() => void
listen
() => void
onConnected
(callback: VoidFunction) => void
onConnecting
(callback: VoidFunction) => void
onDisconnected
(callback: VoidFunction) => void
onError
<ErrorType>(callback: (event: ErrorType) => void) => void
onEvent
(callback: (response: { data: ExtractListenerDeliveredType<ListenerType>; extra: Record<string, any> }) => void) => void
onReconnecting
(callback: (data: { attempts: number }) => void) => void
onReconnectingFailed
(callback: (data: { attempts: number }) => void) => void
setConnected
(connected: boolean) => void
setConnecting
(connecting: boolean) => void
setData
(data: null | TSelected) => void
setExtra
(extra: null | ExtractSocketExtraType<ListenerType[socket]>) => void
setTimestamp
(timestamp: null | number) => void