use-truapi
Chain

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

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 ParameterDefault type
T-
K extends stringstring

Parameters

ParameterType
select(api, client) => object
depsreadonly 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 ParameterDefault type
T-
K extends stringstring

Parameters

ParameterTypeDefault value
select(api, client) => objectundefined
depsreadonly unknown[][]
options?ChainScope<K> & objectundefined

Returns

QueryResult<T>

On this page