useSocketClient
Creates a component's Socket client with explicit URL and contract, closing it on unmount and using its error observer.
import { useSocketClient } from "@meekojs/meeko/framework";useSocketClient is createSocketClient tied to a component: it creates the client with the URL and contract you pass, closes it with onCleanup when the component unmounts (also on page changes) and, without its own onError, reports failures to the nearest provideErrorHandler or the application's onError.
This is the low-level form, for a channel outside your application or with a computed URL. For server/socket channels, the auto-import useSocket("chat", opciones) already knows the URL and generated contract; it is not this function. Since 1.1, useSocket from @meekojs/meeko/framework is a deprecated alias for useSocketClient.
Usage#
<!-- app/components/Ticker.meeko -->
<script lang="ts">
import { signal } from "@meekojs/meeko";
import { useSocketClient } from "@meekojs/meeko/framework";
import { tickerContract } from "../../shared/ticker";
const price = signal<number | undefined>(undefined);
const ticker = await useSocketClient("wss://precios.example.com/_meeko/socket/ticker?room=EURUSD", tickerContract);
ticker.on("price", (data) => { price.value = data.value; });
</script>
<template>
<p>EUR/USD: {price.value ?? "…"}</p>
</template>With await, the server renders the component up to that line (here, EUR/USD: …) and the rest of setup continues in the browser once the socket is open.
Type#
function useSocketClient<C extends SocketContract>(
url: string,
contract: C,
options?: SocketClientOptions,
): SocketClient<C>interface SocketClient<C extends SocketContract> {
readonly state: ReadonlySignal<SocketClientState>;
emit<K extends keyof C["clientToServer"] & string>(
event: K,
data: SchemaInput<C["clientToServer"][K]>,
): Promise<void>;
on<K extends keyof C["serverToClient"] & string>(
event: K,
listener: (data: SchemaOutput<C["serverToClient"][K]>) => void,
): () => void;
close(): void;
}interface SocketClientOptions {
readonly retryMs?: number
readonly maxRetryMs?: number
readonly maxMessageBytes?: number
readonly webSocketFactory?: (url: string) => WebSocket
readonly onError?: (error: unknown) => void
}Parameters#
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
text | required | Channel ws: or wss: URL, with ?room= if needed. |
contract |
SocketContract |
required | Each event's schemas in each direction. |
options |
SocketClientOptions |
{} |
retryMs, maxRetryMs, maxMessageBytes, webSocketFactory and onError. Without onError, failures go to the component's error observer. |
Return value#
The same SocketClient as createSocketClient (state, emit, on, close), automatically closed on component unmount. With await, the expression resolves when the socket is open.
In an HTML-only render in the browser, it returns an inert client without a connection, whose emit rejects with Socket is disconnected.
Examples#
A chat with connection state#
<!-- app/components/Chat.meeko -->
<script lang="ts">
import { effect, signal, untrack } from "@meekojs/meeko";
import { useSocketClient } from "@meekojs/meeko/framework";
import type { SocketClient } from "@meekojs/meeko/framework";
import { chatContract } from "../../shared/chat";
const props = defineProps<{ room: string }>();
const messages = signal<string[]>([]);
const draft = signal("");
const status = signal("connecting");
const connection: { client?: SocketClient<typeof chatContract> } = {};
async function send() {
await connection.client?.emit("message", { text: draft.value });
draft.value = "";
}
const room = untrack(() => props.room);
const chat = await useSocketClient(`wss://chat.example.com/_meeko/socket/chat?room=${encodeURIComponent(room)}`, chatContract, {
onError: (error) => console.warn("chat", error),
});
connection.client = chat;
effect(() => { status.value = chat.state.value; });
chat.on("message", (data) => { messages.value = [...messages.value, data.text]; });
</script>
<template>
<p if:when="status.value !== 'open'">Connecting…</p>
<ul><li each:of="messages.value" each:as="text">{text}</li></ul>
<form on:submit="(event) => { event.preventDefault(); void send(); }">
<input model="draft" aria-label="Message">
<button type="submit">Send</button>
</form>
</template>Everything the template uses (status, messages, draft, send) is declared before await: what follows only exists in the browser. effect copies socket state to a signal already known to the server.
A browser-only page#
<!-- app/pages/live.meeko -->
<script server lang="ts">
export const route = definePage({ render: "client" });
</script>
<script lang="ts">
import { useSocketClient } from "@meekojs/meeko/framework";
import { feedContract } from "../../shared/feed";
const feed = useSocketClient("wss://feed.example.com/_meeko/socket/feed", feedContract);
</script>
<template><p>State: {feed.state.value}</p></template>With render: "client", setup never runs on the server, so await is unnecessary.
Common mistakes#
Emit without await during setup#
<script lang="ts">
import { useSocketClient } from "@meekojs/meeko/framework";
const chat = useSocketClient("wss://example.com/_meeko/socket/chat", chatContract);
void chat.emit("message", { text: "hello" });
</script>Setup also runs on the server, where there are no sockets. Since 1.7, useSocketClient returns an inert client there, remaining "connecting" (through 1.6, rendering failed with useSocketClient() is only available in the browser), so the call no longer breaks SSR; but on the server, and in the browser before the socket opens, emit rejects with Socket is disconnected.
<script lang="ts">
import { useSocketClient } from "@meekojs/meeko/framework";
const chat = await useSocketClient("wss://example.com/_meeko/socket/chat", chatContract);
void chat.emit("message", { text: "hello" });
</script>With await, the server stops before that line and the browser continues setup with the socket open.
Use the client declared after await in the template#
<script lang="ts">
const chat = await useSocketClient("wss://example.com/_meeko/socket/chat", chatContract);
</script>
<template><p>{chat.state.value}</p></template>meeko check reports MEEK057: La plantilla no puede usar chat: se declara en la línea 2, después del primer await de navegador, y sólo existe en el navegador una vez montado el componente. Declara ese nombre antes de ese await.
<script lang="ts">
import { effect, signal } from "@meekojs/meeko";
const status = signal("connecting");
const chat = await useSocketClient("wss://example.com/_meeko/socket/chat", chatContract);
effect(() => { status.value = chat.state.value; });
</script>
<template><p>{status.value}</p></template>Call it outside setup#
button.addEventListener("click", () => {
const chat = useSocketClient(url, chatContract);
});Outside component setup, there is no root to attach cleanup to: the client closes and the call throws onCleanup() requires an active component root.
button.addEventListener("click", () => {
const chat = createSocketClient(url, chatContract);
window.addEventListener("pagehide", () => chat.close(), { once: true });
});Confuse it with the useSocket auto-import#
<script lang="ts">
const chat = useSocketClient("chat");
</script>useSocketClient receives a URL and contract: meeko check reports TS2554: Expected 2-3 arguments, but got 1. The useSocket auto-import receives the channel name and knows its contract.
<script lang="ts">
const chat = useSocket("chat");
</script>Notes#
- SSR. Never opens a connection on the server. Without
await, since1.7the call returns an inert client in SSR:stateremains"connecting",onregisters nothing andemitrejects; through1.6, it threw. Withawait, the server renders up to that line and the browser continues; design the template so the first render does not depend on the socket. - Lifecycle. The client closes with the component: navigating to another page, removing it with
if:whenor closing the application withdispose(). - Errors. Without
onError, invalid messages and listener failures reach the nearestprovideErrorHandlerormountClientApp'sonError, like other component errors. - Deprecated.
useSocketimported from@meekojs/meeko/frameworkis an alias for this function since1.1and disappears in2.0; the editor marks it struck through. Applications'useSocket(canal)auto-import is not deprecated.