kererūK
tRPC3y ago
3 replies
kererū

How to type a helper function to standardise loading and error states with a useQuery call?

I am trying to standardize handling of error and loading states. I'd like to have one function/component that can handle what to do in those cases, and otherwise passes
data
to a function to render whatever is the happy path.

So something like this:

function RenderHelper({useQueryOutput, renderData}) {
  const {data, error, isFetching, isSuccess, isError} = useQueryOutput;
  if (isError) { /* ... render `error` UI */ }
  if (isLoading) { /* ... render loading UI  */}
  if (isSuccess) {
    return renderData(data);
  }
}

const SomeComponent = () => {
  const userQuery = trpc.user.myInfo.useQuery();
  return <RenderHelper useQueryOutput={userQuery} renderData={(data) => {
      return <div>{data.userName}</div>; // structure of `data` should be inferred
    }
  } />
}


I'm struggling to type this in a way that the
renderData
function has the structure of
data
inferred by Typescript. I have looked at
AppRouterLike
,
QueryLike
and friends but can't get it to work. Would love some assistance, in the meantime I'll keep bashing my head against it. Here's a not-working example, in which
data
is typed as
any
and in the
render
function TS thinks
data
could be
undefined


export function RenderQuery<T extends ReturnType<QueryLike["useQuery"]>>({
  useQueryResponse,
  render,
}: {
  useQueryResponse: T;
  render: (data: T["data"]) => ReactElement;
}) {
  const { data, error, isFetching, isError, isSuccess } = useQueryResponse;
  if (isFetching) {
    return <LoadingSpinner />;
  }
  if (isError) {
    return (
        <Typography>Sorry, something went wrong.</Typography>
    );
  }
  if (isSuccess) {
    if (data === undefined) {
      throw new Error("How does TS still think data might be undefined in `render`?");
    }
    return render(data);
  }
  throw new Error("Shouldn't be possible to reach this point");
}
Solution
In case it helps anyone, this seems to work

export function RenderQuery<
  T extends QueryLike,
  G extends InferQueryLikeInput<T>,
  P extends InferQueryLikeData<T>,
>({
  useQuery,
  input,
  render,
}: {
  useQuery: T;
  input: G;
  render: (data: P) => ReactElement;
}) {
  const query = useQuery.useQuery(input);
  switch (query.status) {
    case "error":
      return <div>Sorry, something went wrong.</div>
    case "loading":
      return <LoadingSpinner />;
    case "success":
      return render(query.data);
    default:
      const _exhaustivenessCheck: never = query;
      return _exhaustivenessCheck;
  }
}


Called like

<RenderQuery 
  useQuery={trpc.user.details} 
  input={{userId}} 
  render={(data) => <div>{data.username}</div>} />
Was this page helpful?