Skip to Content
โฑ๏ธ Realtime UI (DRAFTS) ๐ŸšงFetching and Normalizing the Data

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:

src/lib/fetcher.ts
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.

src/lib/fetcher.ts
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.

vovk.config.mjs
// @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.