Last active
May 12, 2022 21:00
-
-
Save KATT/aa1ad532d6e57520b942f657569e1505 to your computer and use it in GitHub Desktop.
A suspense hook for tRPC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}</>; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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