Setting up the fetcher
The next piece of the puzzle is to create a fetcher function that is going to be used by the application to request data from the server. You can find more details about it at imports article.
The fetcher is going to implement transformResponse function that is going to process all incoming data, passing it to the parse method of the registry.
In the simplest form, when we expect JSON responses only, the function is going to look like this:
import { useRegistry } from "@/registry";
import { createFetcher } from "vovk";
export const fetcher = createFetcher({
transformResponse: async (data, { bypassRegistry }) => {
const state = useRegistry.getState();
state.parse(data);
return data;
},
});As our app is also going to support JSONLines responses for streaming data, weโre going to enhance the function a bit with another if check.
Itโs also good to have an option to bypass the registry for specific requests, so weโre going to add bypassRegistry option to the fetcher config. Itโs declared as createFetcher generic parameter and used as an otpion of the second argument of transformResponse function. It can be used as follows: await UserRPC.getUsers({ bypassRegistry: true }). The same way you can add more options in the future if needed.
import { useRegistry } from "@/registry";
import { createFetcher, HttpStatus } from "vovk";
export const fetcher = createFetcher<{ bypassRegistry?: boolean }>({
transformResponse: async (data, { bypassRegistry }) => {
if (bypassRegistry) {
return data;
}
const state = useRegistry.getState();
if (
data &&
typeof data === "object" &&
Symbol.asyncIterator in data &&
"onIterate" in data &&
typeof data.onIterate === "function"
) {
data.onIterate(state.parse); // handle each item in the async iterable
return data;
}
state.parse(data); // parse regular JSON data
return data;
},
onError: (error) => {
if (error.statusCode === HttpStatus.UNAUTHORIZED) {
document.location.href = "/login";
}
},
});The code above is fetched from GitHub repository.ย
Declare the fetcher in the config. It will replace the default fetcher imported by the generated client.
// @ts-check
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
imports: {
// ...
fetcher: './src/lib/fetcher.ts',
},
},
};
export default config;Thatโs it. From now on, each request is going to be processed by the registry parse method, and manual response handling is not required anymore. Here is a rough example:
import { useShallow } from "zustand/shallow";
import { useQuery } from "@tanstack/react-query";
import { UserRPC } from "vovk-client";
import { useRegistry } from "@/registry";
import { UserType } from "../../prisma/generated/schemas/models/User.schema";
interface Props {
userIds: UserType["id"][]; // an array of branded user IDs
}
const UsersExample = ({ userIds }: Props) => {
// request the data somewhere in the app
useQuery({
queryKey: UserRPC.getUsers.queryKey(),
queryFn: () => UserRPC.getUsers(),
});
// retrieve users from the registry
const users = useRegistry(
useShallow((state) => userIds.map((id) => state.user[id])),
);
return <div>
<ul>
{users.map((user) => (
<li key={user.id}>{user.fullName}</li>
))}
</ul>
</div>;
};
export default UsersExample;As you can see useQuery invocation doesnโt require to read data property anymore, as the response is processed by the registry automatically. The invocation of UserRPC.getUsers() or any other RPC method (with useQuery or without) can be placed anywhere in the app, and all components that use user data are going to be updated automatically.