useChainSubscription
Live subscription to any typed-api observable, shared
useChainSubscription subscribes to any observable exposed by the typed PAPI
api or the raw client (storage watches, event streams, block streams) and
surfaces the latest value as data. Your select callback receives the
typed api and the PolkadotClient and returns anything with a
subscribe(observer) method.
The subscription is shared: any number of components mounting the same
(chain, deps) pair hold a single subscription, dropped when the last
consumer unmounts. Like useChainQuery,
deps identify the subscription: change a dep and the hook re-attaches under
the new inputs. If the stream errors, the result flips into the error state
but the last received data is kept. While enabled is false nothing is
attached.
Use this for values that should track the chain head; for a read that only
needs to happen once, use useChainQuery.
Usage
import { useChainSubscription } from "@use-truapi/react";
function Clock() {
const { data: now, isPending, error } = useChainSubscription(
(api) => api.query.Timestamp.Now.watchValue(),
[],
);
if (isPending) return <p>Waiting for the chain…</p>;
if (error) return <p>Subscription failed: {error.message}</p>;
return <p>On-chain time: {new Date(Number(now)).toLocaleTimeString()}</p>;
}<script setup lang="ts">
import { useChainSubscription } from "@use-truapi/vue";
import { computed } from "vue";
const { data: now, isPending, error } = useChainSubscription(
(api) => api.query.Timestamp.Now.watchValue(),
[],
);
const time = computed(() =>
now.value !== undefined
? new Date(Number(now.value)).toLocaleTimeString()
: "",
);
</script>
<template>
<p v-if="isPending">Waiting for the chain…</p>
<p v-else-if="error">Subscription failed: {{ error.message }}</p>
<p v-else>On-chain time: {{ time }}</p>
</template>Anything the observable closes over belongs in deps, so the subscription
re-attaches when it changes (in Vue, pass getters):
const { data: account } = useChainSubscription(
(api) => api.query.System.Account.watchValue(address, { at: "best" }),
[address],
{ enabled: address !== undefined },
);select also receives the raw PolkadotClient, so client streams work too.
Give such subscriptions a distinguishing dep (a string label works) so they
don't collide with other empty-deps subscriptions on the same chain:
const { data: finalized } = useChainSubscription(
(_api, client) => client.finalizedBlock$,
["finalizedBlock"],
);Values are pushed, not polled. There is nothing to refetch; new values arrive on their own, and the result settles once the first value lands.
See also
useChainQueryfor one-shot reads without a live subscription.useBlockNumberis a ready-made best-block watch.useBalanceis a ready-made native balance watch.
API reference
React
function useChainSubscription<T, K>(
select,
deps,
options?): UseQueryResult<T, Error>;Live subscription to any typed-api observable (storage watch, events, …); one shared subscription per key, values bridged into the query cache, automatically unsubscribed when the last consumer unmounts.
const now = useChainSubscription(
(api) => api.query.Timestamp.Now.watchValue(),
[],
);Type Parameters
| Type Parameter | Default type |
|---|---|
T | - |
K extends string | string |
Parameters
| Parameter | Type |
|---|---|
select | (api, client) => object |
deps | readonly unknown[] |
options? | ChainScope<K> & object |
Returns
UseQueryResult<T, Error>
Vue
function useChainSubscription<T, K>(
select,
deps?,
options?): QueryResult<T>;Live subscription to any typed-api observable (storage watch, events, …); one shared subscription per key, values bridged into the query cache, torn down when the last consumer's scope disposes.
Type Parameters
| Type Parameter | Default type |
|---|---|
T | - |
K extends string | string |
Parameters
| Parameter | Type | Default value |
|---|---|---|
select | (api, client) => object | undefined |
deps | readonly unknown[] | [] |
options? | ChainScope<K> & object | undefined |
Returns
QueryResult<T>