kererū
kererū11mo ago

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
}
} />
}
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");
}
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 ```ts export function RenderQuery< T extends QueryLike,...
Jump to solution
1 Reply
Solution
kererū
kererū11mo ago
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;
}
}
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>} />
<RenderQuery
useQuery={trpc.user.details}
input={{userId}}
render={(data) => <div>{data.username}</div>} />