Skip to content
tRPC

useSubscription()

On this page

The useSubscription hook can be used to subscribe to a subscription procedure on the server.

Signature

Options

interface interface UseTRPCSubscriptionOptions<TOutput, TError>UseTRPCSubscriptionOptions<function (type parameter) TOutput in UseTRPCSubscriptionOptions<TOutput, TError>TOutput, function (type parameter) TError in UseTRPCSubscriptionOptions<TOutput, TError>TError> {
  /**
   * Called when the subscription is started.
   */
  UseTRPCSubscriptionOptions<TOutput, TError>.onStarted?: (() => void) | undefinedonStarted?: () => void;
  /**
   * Called when new data is received from the subscription.
   */
  UseTRPCSubscriptionOptions<TOutput, TError>.onData?: ((data: TOutput) => void) | undefinedonData?: (data: TOutputdata: function (type parameter) TOutput in UseTRPCSubscriptionOptions<TOutput, TError>TOutput) => void;
  /**
   * Called when an **unrecoverable error** occurs and the subscription is stopped.
   */
  UseTRPCSubscriptionOptions<TOutput, TError>.onError?: ((error: TError) => void) | undefinedonError?: (error: TErrorerror: function (type parameter) TError in UseTRPCSubscriptionOptions<TOutput, TError>TError) => void;
  /**
   * Called when the subscription is completed on the server.
   * The state will transition to `'idle'` with `data: undefined`.
   */
  UseTRPCSubscriptionOptions<TOutput, TError>.onComplete?: (() => void) | undefinedonComplete?: () => void;
  /**
   * @deprecated Use a `skipToken` from `@tanstack/react-query` instead.
   * This will be removed in v12.
   */
  UseTRPCSubscriptionOptions<TOutput, TError>.enabled?: boolean | undefinedenabled?: boolean;
}

Return type

The return type is a discriminated union on status:

type type TRPCSubscriptionResult<TOutput, TError> = TRPCSubscriptionIdleResult<TOutput> | TRPCSubscriptionConnectingResult<TOutput, TError> | TRPCSubscriptionPendingResult<TOutput> | TRPCSubscriptionErrorResult<TOutput, TError>TRPCSubscriptionResult<function (type parameter) TOutput in type TRPCSubscriptionResult<TOutput, TError>TOutput, function (type parameter) TError in type TRPCSubscriptionResult<TOutput, TError>TError> =
  | interface TRPCSubscriptionIdleResult<TOutput>TRPCSubscriptionIdleResult<function (type parameter) TOutput in type TRPCSubscriptionResult<TOutput, TError>TOutput>
  | interface TRPCSubscriptionConnectingResult<TOutput, TError>TRPCSubscriptionConnectingResult<function (type parameter) TOutput in type TRPCSubscriptionResult<TOutput, TError>TOutput, function (type parameter) TError in type TRPCSubscriptionResult<TOutput, TError>TError>
  | interface TRPCSubscriptionPendingResult<TOutput>TRPCSubscriptionPendingResult<function (type parameter) TOutput in type TRPCSubscriptionResult<TOutput, TError>TOutput>
  | interface TRPCSubscriptionErrorResult<TOutput, TError>TRPCSubscriptionErrorResult<function (type parameter) TOutput in type TRPCSubscriptionResult<TOutput, TError>TOutput, function (type parameter) TError in type TRPCSubscriptionResult<TOutput, TError>TError>;

interface interface TRPCSubscriptionIdleResult<TOutput>TRPCSubscriptionIdleResult<function (type parameter) TOutput in TRPCSubscriptionIdleResult<TOutput>TOutput> {
  /** Subscription is disabled or has ended */
  TRPCSubscriptionIdleResult<TOutput>.status: "idle"status: 'idle';
  TRPCSubscriptionIdleResult<TOutput>.data: undefineddata: undefined;
  TRPCSubscriptionIdleResult<TOutput>.error: nullerror: null;
  TRPCSubscriptionIdleResult<TOutput>.reset: () => voidreset: () => void;
}

interface interface TRPCSubscriptionConnectingResult<TOutput, TError>TRPCSubscriptionConnectingResult<function (type parameter) TOutput in TRPCSubscriptionConnectingResult<TOutput, TError>TOutput, function (type parameter) TError in TRPCSubscriptionConnectingResult<TOutput, TError>TError> {
  /** Trying to establish a connection (may have a previous error from a reconnection attempt) */
  TRPCSubscriptionConnectingResult<TOutput, TError>.status: "connecting"status: 'connecting';
  TRPCSubscriptionConnectingResult<TOutput, TError>.data: TOutput | undefineddata: function (type parameter) TOutput in TRPCSubscriptionConnectingResult<TOutput, TError>TOutput | undefined;
  TRPCSubscriptionConnectingResult<TOutput, TError>.error: TError | nullerror: function (type parameter) TError in TRPCSubscriptionConnectingResult<TOutput, TError>TError | null;
  TRPCSubscriptionConnectingResult<TOutput, TError>.reset: () => voidreset: () => void;
}

interface interface TRPCSubscriptionPendingResult<TOutput>TRPCSubscriptionPendingResult<function (type parameter) TOutput in TRPCSubscriptionPendingResult<TOutput>TOutput> {
  /** Connected to the server, receiving data */
  TRPCSubscriptionPendingResult<TOutput>.status: "pending"status: 'pending';
  TRPCSubscriptionPendingResult<TOutput>.data: TOutput | undefineddata: function (type parameter) TOutput in TRPCSubscriptionPendingResult<TOutput>TOutput | undefined;
  TRPCSubscriptionPendingResult<TOutput>.error: nullerror: null;
  TRPCSubscriptionPendingResult<TOutput>.reset: () => voidreset: () => void;
}

interface interface TRPCSubscriptionErrorResult<TOutput, TError>TRPCSubscriptionErrorResult<function (type parameter) TOutput in TRPCSubscriptionErrorResult<TOutput, TError>TOutput, function (type parameter) TError in TRPCSubscriptionErrorResult<TOutput, TError>TError> {
  /** An unrecoverable error occurred and the subscription is stopped */
  TRPCSubscriptionErrorResult<TOutput, TError>.status: "error"status: 'error';
  TRPCSubscriptionErrorResult<TOutput, TError>.data: TOutput | undefineddata: function (type parameter) TOutput in TRPCSubscriptionErrorResult<TOutput, TError>TOutput | undefined;
  TRPCSubscriptionErrorResult<TOutput, TError>.error: TErrorerror: function (type parameter) TError in TRPCSubscriptionErrorResult<TOutput, TError>TError;
  TRPCSubscriptionErrorResult<TOutput, TError>.reset: () => voidreset: () => void;
}

Example Procedure

server/routers/_app.ts
import 
class EventEmitter<T extends EventMap<T> = any>
interface EventEmitter<T extends EventMap<T> = any>
EventEmitter
, { function on(emitter: EventEmitter, eventName: string | symbol, options?: EventEmitter.OnOptions): NodeJS.AsyncIterator<any[]> (+1 overload)on } from 'events';
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server'; export const
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.
TRPCBuilder<object, object>.create<RuntimeConfigOptions<object, object>>(opts?: RuntimeConfigOptions<object, object> | undefined): TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
type
type Post = {
    id: string;
    title: string;
}
Post
= { id: stringid: string; title: stringtitle: string };
const const ee: EventEmitter<any>ee = new new EventEmitter<any>(options?: EventEmitter.EventEmitterOptions): EventEmitter<any>EventEmitter(); export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
onPostAdd: SubscriptionProcedure<{
    input: void;
    output: AsyncIterable<Post, void, any>;
    meta: object;
    errorShape: DefaultErrorShape;
}>
onPostAdd
:
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.subscription<AsyncGenerator<Post, void, any>>(resolver: ProcedureResolver<object, object, object, UnsetMarker, UnsetMarker, AsyncGenerator<Post, void, any>>): SubscriptionProcedure<{
    input: void;
    output: AsyncIterable<Post, void, any>;
    meta: object;
    errorShape: DefaultErrorShape;
}> (+1 overload)
subscription
(async function* (opts: ProcedureResolverOptions<object, object, object, UnsetMarker>opts) {
for await (const [const data: anydata] of function on(emitter: EventEmitter, eventName: string | symbol, options?: EventEmitter.OnOptions): NodeJS.AsyncIterator<any[]> (+1 overload)on(const ee: EventEmitter<any>ee, 'add', { EventEmitter<T extends EventMap<T> = any>.Abortable.signal?: AbortSignal | undefinedsignal: opts: ProcedureResolverOptions<object, object, object, UnsetMarker>opts.ProcedureResolverOptions<object, object, object, UnsetMarker>.signal: AbortSignal | undefinedsignal, })) { const const post: Postpost = const data: anydata as
type Post = {
    id: string;
    title: string;
}
Post
;
yield const post: Postpost; } }), }); export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

Example React Component

components/PostFeed.tsx
import { 
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
trpc
} from '../utils/trpc';
type
type Post = {
    id: string;
    title: string;
}
Post
= { id: stringid: string; title: stringtitle: string };
export function function PostFeed(): React.JSX.ElementPostFeed() { const [const posts: Post[]posts, const setPosts: React.Dispatch<React.SetStateAction<Post[]>>setPosts] = React.function React.useState<Post[]>(initialState: Post[] | (() => Post[])): [Post[], React.Dispatch<React.SetStateAction<Post[]>>] (+1 overload)useState<
type Post = {
    id: string;
    title: string;
}
Post
[]>([]);
const
const subscription: TRPCSubscriptionResult<Post, TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
subscription
=
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    onPostAdd: SubscriptionProcedure<{
        input: void;
        output: AsyncIterable<Post, void, any>;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
trpc
.
onPostAdd: {
    useSubscription: ProcedureUseSubscription<{
        input: void;
        output: AsyncIterable<Post, never, any>;
        transformer: false;
        errorShape: DefaultErrorShape;
    }>;
}
onPostAdd
.
useSubscription: ProcedureUseSubscription
(input: void, opts?: UseTRPCSubscriptionOptions<Post, TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>> | undefined) => TRPCSubscriptionResult<Post, TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>> (+1 overload)
useSubscription
(var undefinedundefined, {
UseTRPCSubscriptionOptions<Post, TRPCClientErrorLike<{ input: void; output: AsyncIterable<Post, never, any>; transformer: false; errorShape: DefaultErrorShape; }>>.onData?: ((data: Post) => void) | undefinedonData: (post: Postpost) => { const setPosts: (value: React.SetStateAction<Post[]>) => voidsetPosts((prev: Post[]prev) => [...prev: Post[]prev, post: Postpost]); }, }); return ( <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> <React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>Live Feed</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1> {
const subscription: TRPCSubscriptionResult<Post, TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
subscription
.status: "idle" | "connecting" | "error" | "pending"status === 'connecting' && <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Connecting...</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>}
{
const subscription: TRPCSubscriptionResult<Post, TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
subscription
.status: "idle" | "connecting" | "error" | "pending"status === 'error' && (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Error: {
const subscription: TRPCSubscriptionErrorResult<Post, TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
subscription
.
TRPCSubscriptionErrorResult<Post, TRPCClientErrorLike<{ input: void; output: AsyncIterable<Post, never, any>; transformer: false; errorShape: DefaultErrorShape; }>>.error: TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>
error
.TRPCClientErrorBase<DefaultErrorShape>.message: stringmessage}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() =>
const subscription: TRPCSubscriptionErrorResult<Post, TRPCClientErrorLike<{
    input: void;
    output: AsyncIterable<Post, never, any>;
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
subscription
.TRPCSubscriptionBaseResult<Post, TRPCClientErrorLike<{ input: void; output: AsyncIterable<Post, never, any>; transformer: false; errorShape: DefaultErrorShape; }>>.reset: () => voidreset()}>Reconnect</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> )} <React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul> {const posts: Post[]posts.Array<Post>.map<React.JSX.Element>(callbackfn: (value: Post, index: number, array: Post[]) => React.JSX.Element, thisArg?: any): React.JSX.Element[]map((post: Postpost) => ( <React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li React.Attributes.key?: React.Key | null | undefinedkey={post: Postpost.id: stringid}>{post: Postpost.title: stringtitle}</React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li> ))} </React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul> </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> ); }