trpc-svelte-query-adapter
Version:
A simple adapter to use `@tanstack/svelte-query` with trpc, similar to `@trpc/react-query`.
331 lines (253 loc) • 10.8 kB
Markdown
# `tRPC` - `svelte-query` Adapter
[![NPM version][npm-image]][npm-url]
[![License][license-image]][license-url]
[![Last commit][last-commit-image]][repo-url]
> [!NOTE]
> The README on [npmjs](https://npmjs.com/trpc-svelte-query-adapter) might not be fully up to date. Please refer to
> the [README on the Github Repo](https://github.com/vishalbalaji/trpc-svelte-query-adapter/#readme) for the latest setup instructions.
An adapter to call `tRPC` procedures wrapped with <code>[/svelte-query](https://tanstack.com/query/latest/docs/svelte/overview)</code>, similar to <code>[/react-query](https://trpc.io/docs/react-query)</code>. This is made possible using <code>[proxy-deep](https://www.npmjs.com/package/proxy-deep)</code>.
## Installation
```sh
# npm
npm install trpc-svelte-query-adapter /client @trpc/server /svelte-query
# yarn
yarn add trpc-svelte-query-adapter /client @trpc/server /svelte-query
# pnpm
pnpm add trpc-svelte-query-adapter /client @trpc/server /svelte-query
```
If you are using client-side Svelte, you would need to install `/server` as a `devDependency` using `--save-dev`.
## Available Functions
The following functions from `/react-query` are ported over:
- `useQuery` -> `createQuery`
- `useInfiniteQuery` -> `createInfiniteQuery`
- `useMutation` -> `createMutation`
- `useSubscription` -> `createSubscription`
- `useQueries` -> `createQueries`
- `useUtils` -> `createUtils`
- `getQueryKey`
You can refer to <code>[tanstack-query docs](https://tanstack.com/query/latest/docs/react/overview)</code> and <code>[@trpc/react-query docs](https://trpc.io/docs/react-query)</code> for documentation on how to use them.
There are also some new procedures that are only relevant for SvelteKit:
- `createServerQuery`
- `createServerInfiniteQuery`
- `createServerQueries`
As for these procedures, you can refer to the [Server-Side Query Pre-Fetching](#server-side-query-pre-fetching) section.
## Usage
The following instructions assume the `tRPC` router to have the following procedures:
```typescript
export const router = t.router({
greeting: t.procedure
.input((name: unknown) => {
if (typeof name === 'string') return name;
throw new Error(`Invalid input: ${typeof name}`);
})
.query(async ({ input }) => {
return `Hello, ${input} from tRPC v10 @ ${new Date().toLocaleTimeString()}`;
}),
});
export type Router = typeof router;
```
### Client-Only Svelte
1. Setup `/svelte-query` as per [svelte-query docs](https://tanstack.com/query/latest/docs/svelte/overview).
2. Setup <code>[/client](https://trpc.io/docs/client)</code> and export the `tRPC` client.
3. Wrap the exported `tRPC` client with `svelteQueryWrapper` from `trpc-svelte-query-adapter`, as demonstrated in the example below:
```typescript
// src/lib/trpc.ts
import type { Router } from '/path/to/trpc/router';
import { createTRPCProxyClient, httpBatchLink } from '/client';
import { svelteQueryWrapper } from 'trpc-svelte-query-adapter';
const client = createTRPCProxyClient<Router>({
links: [
httpBatchLink({
// Replace this URL with that of your tRPC server
url: 'http://localhost:5000/api/v1/trpc/',
}),
],
});
export const trpc = svelteQueryWrapper<Router>({ client });
```
4. The exported `tRPC` client can then be used in `svelte` components as follows:
```svelte
<script lang="ts">
import { trpc } from "/path/to/lib/trpc";
const foo = trpc.greeting.createQuery('foo', { retry: false });
</script>
{#if $foo.isPending}
Loading...
{:else if $foo.isError}
Error: {$foo.error.message}
{:else if $foo.data}
{$foo.data.message}
{/if}
```
### SvelteKit and SSR
For SvelteKit, the process is pretty much the same as for client-only svelte. However, if you intend to call queries from the server in a `load` function, you would need to setup `/svelte-query` according to the [the ssr example in the svelte-query docs](https://tanstack.com/query/latest/docs/svelte/ssr#using-prefetchquery).
Upon doing that, you would also need to pass in the `queryClient` to `svelteQueryWrapper` when initializing on the server, which you can get by calling the `event.parent` method in the `load` function. You can see an example of this in the [Server-Side Query Pre-Fetching](#server-side-query-pre-fetching) section. For this purpose, you might also want to export your client wrapped in a function that optionally takes in `queryClient` and passes it onto `svelteQueryWrapper`.
Here is an example of what that might look like:
```typescript
import type { QueryClient } from '/svelte-query';
const client = createTRPCProxyClient<Router>({
links: [
httpBatchLink({
// Replace this URL with that of your tRPC server
url: 'http://localhost:5000/api/v1/trpc/',
}),
],
});
export function trpc(queryClient?: QueryClient) {
return svelteQueryWrapper<Router>({
client,
queryClient,
});
}
```
Which can then be used in a component as such:
```svelte
<!-- routes/+page.svelte -->
<script lang="ts">
import { trpc } from "$lib/trpc/client";
const client = trpc();
const foo = client.greeting.createQuery("foo", { retry: false });
</script>
<p>
{#if $foo.isPending}
Loading...
{:else if $foo.isError}
Error: {$foo.error.message}
{:else}
{$foo.data}
{/if}
</p>
```
The main thing that needs to passed in to `svelteQueryWrapper` is the `tRPC` client itself. So, this adapter should support different implementations of `tRPC` for Svelte and SvelteKit. For example, if you are using <code>[trpc-sveltekit by icflorescu](https://icflorescu.github.io/trpc-sveltekit)</code>, all you would need to do after setting it up would be to change the client initialization function from something like this:
```typescript
let browserClient: ReturnType<typeof createTRPCClient<Router>>;
export function trpc(init?: TRPCClientInit) {
const isBrowser = typeof window !== 'undefined';
if (isBrowser && browserClient) return browserClient;
const client = createTRPCClient<Router>({ init });
if (isBrowser) browserClient = client;
return client;
}
```
to this:
```typescript
import { svelteQueryWrapper } from 'trpc-svelte-query-adapter';
import type { QueryClient } from '/svelte-query';
let browserClient: ReturnType<typeof svelteQueryWrapper<Router>>;
export function trpc(init?: TRPCClientInit, queryClient?: QueryClient) {
const isBrowser = typeof window !== 'undefined';
if (isBrowser && browserClient) return browserClient;
const client = svelteQueryWrapper<Router>({
client: createTRPCClient<Router>({ init }),
queryClient,
});
if (isBrowser) browserClient = client;
return client;
}
```
Which can then be initialized and used in the way that it is described [in its docs](https://icflorescu.github.io/trpc-sveltekit/getting-started).
#### Server-Side Query Pre-Fetching
This adapter provides 3 additional procedures: `createServerQuery`, `createServerInfiniteQuery` and `createServerQueries`, which can be used to call their counterpart procedures in the `load` function in either a `+page.ts` or `+layout.ts`. These procedures return a `promise` and therefore can only really be called on the server.
By default, these 3 procedures will pre-fetch the data required to pre-render the page on the server. However, if you wish to disable this behaviour on certain queries, you can do so by setting the `ssr` option to `false`.
These procedures can be used as such:
> [!NOTE]
> [Gotta await top-level promises to pre-fetch data from SvelteKit v2](https://kit.svelte.dev/docs/migrating-to-sveltekit-2#top-level-promises-are-no-longer-awaited).
```typescript
// +page.ts
// tRPC is setup using `trpc-sveltekit` for this example.
import { trpc } from '$lib/trpc/client';
import type { PageLoad } from './$types';
export const load = (async (event) => {
const { queryClient } = await event.parent();
const client = trpc(event, queryClient);
return {
foo: await client.greeting.createServerQuery('foo'),
queries: await client.createServerQueries(
(t) =>
['bar', 'baz'].map((name) => t.greeting(name, { ssr: name !== 'baz' })) // pre-fetching disabled for the `baz` query.
),
};
}) satisfies PageLoad;
```
Then, in the component:
```svelte
<!-- +page.svelte -->
<script lang="ts">
import { page } from "$app/stores";
import type { PageData } from "./$types";
export let data: PageData;
const foo = data.foo();
const queries = data.queries();
</script>
{#if $foo.isPending}
Loading...
{:else if $foo.isError}
{$foo.error}
{:else if $foo.data}
{$foo.data}
{/if}
<br /><br />
{#each $queries as query}
{#if query.isPending}
Loading...
{:else if query.isError}
{query.error.message}
{:else if query.data}
{query.data}
{/if}
<br />
{/each}
```
You can also optionally pass new inputs to the queries and infinite queries from the client side(see [#34](/../../issues/34), [#47]( /../../issues/47 )) like so:
```svelte
<script lang="ts">
import { page } from "$app/stores";
import type { PageData } from "./$types";
import { derived, writable } from '/store';
export let data: PageData;
const name = writable('foo');
const newNames = writable<string[]>([]);
const foo = data.foo($name);
// You can also access the default input if you pass in a callback as the new input:
// const foo = data.foo((old) => derived(name, ($name) => old + name));
const queries = data.queries((t, old) => derived(newNames, ($newNames) => [...old, ...$newNames.map((name) => t.greeting(name))]));
</script>
<div>
{#if $foo.isPending}
Loading...
{:else if $foo.isError}
{$foo.error}
{:else if $foo.data}
{$foo.data}
{/if}
<input bind:value={$name} />
</div>
<br />
<div>
{#each $queries as query}
{#if query.isPending}
Loading...
{:else if query.isError}
{query.error.message}
{:else if query.data}
{query.data}
{/if}
<br />
{/each}
<form on:submit|preventDefault={(e) => {
const data = new FormData(e.currentTarget).get('name');
if (typeof data === 'string') $newNames.push(data);
$newNames = $newNames;
}}>
<input name="name" />
<button type="submit">Submit</button>
</form>
</div>
```
For more usage examples, you can refer to the [example app provided in the repo](/ ).
[npm-url]: https://npmjs.org/package/trpc-svelte-query-adapter
[npm-image]: https://img.shields.io/npm/v/trpc-svelte-query-adapter.svg
[license-url]: LICENSE
[license-image]: http://img.shields.io/npm/l/trpc-svelte-query-adapter.svg
[repo-url]: https://github.com/vishalbalaji/trpc-svelte-query-adapter
[last-commit-image]: https://img.shields.io/github/last-commit/vishalbalaji/trpc-svelte-query-adapter