Skip to content

Instantly share code, notes, and snippets.

@KATT
Last active May 12, 2022 21:00
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KATT/aa1ad532d6e57520b942f657569e1505 to your computer and use it in GitHub Desktop.
Save KATT/aa1ad532d6e57520b942f657569e1505 to your computer and use it in GitHub Desktop.
A suspense hook for tRPC
import React, { Component, ErrorInfo, ReactNode } from 'react';
import NextError from 'next/error';
import { TRPCClientErrorLike } from '@trpc/client';
import { AppRouter } from 'server/routers/_app';
interface Props {
children: ReactNode;
}
interface State {
error?: unknown;
}
export class ErrorBoundary extends Component<Props, State> {
public state: State = {};
public static getDerivedStateFromError(error: Error): State {
// Update state so the next render will show the fallback UI.
return { error: error };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Uncaught error:', error, errorInfo);
}
public render() {
if (this.state.error) {
if (
this.state.error &&
typeof this.state.error === 'object' &&
(this.state.error as any).message
) {
const err = this.state.error as any as TRPCClientErrorLike<AppRouter>;
return (
<NextError
title={err.message}
statusCode={err.data?.httpStatus ?? 500}
/>
);
}
return <NextError title={'Something went wrong'} statusCode={500} />;
}
return <>{this.props.children}</>;
}
}
import type { TRPCClientErrorLike } from '@trpc/client';
import type { UseTRPCQueryOptions } from '@trpc/react';
import type {
inferHandlerInput,
inferProcedureInput,
inferProcedureOutput,
} from '@trpc/server';
import type { QueryObserverSuccessResult } from 'react-query';
import type { AppRouter } from '~/server/routers/_app';
import { trpc } from './trpc';
type TRouter = AppRouter;
type TError = TRPCClientErrorLike<TRouter>;
type TQueries = TRouter['_def']['queries'];
interface UseSuspenseQueryOptions<TPath, TInput, TOutput, TError>
extends UseTRPCQueryOptions<TPath, TInput, TOutput, TError> {
suspense?: true;
enabled?: true;
}
/**
* This hook returns a guaranteed `{ data: TData }` & not `{ data: undefined | TData }`
* Any errors are propagated to nearest error container and loading is propagated to closest `<Suspense />`
*
* tRPC in it's current version was written before I started properly using Suspsense.
* This sort of hook will come in tRPC in the next major and likely become the default behaviour.
* @returns
* A `[TData, QueryObserverSuccessResult]`-tuple, where you usually only need the first part.
*/
export function useSuspenseQuery<TPath extends keyof TQueries & string>(
pathAndInput: [path: TPath, ...args: inferHandlerInput<TQueries[TPath]>],
opts?: UseSuspenseQueryOptions<
TPath,
inferProcedureInput<TQueries[TPath]>,
inferProcedureOutput<TQueries[TPath]>,
TError
>,
): [
inferProcedureOutput<TQueries[TPath]>,
QueryObserverSuccessResult<inferProcedureOutput<TQueries[TPath]>, TError>,
] {
const _opts = opts ?? {};
// enforce `suspense` & `enabled`
_opts.suspense = true;
_opts.enabled = true;
const query = trpc.useQuery(
pathAndInput,
_opts as any,
) as any as QueryObserverSuccessResult<
inferProcedureOutput<TQueries[TPath]>,
TError
>;
return [query.data, query];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment