-
Notifications
You must be signed in to change notification settings - Fork 7
How to stream queries to Pages/Components? #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hey @dihmeetree, to stream stuff from the load function in Sveltekit, you would need to return it as a nested property, something like so: // +page.ts
import { trpc } from '$lib/trpc/client';
import type { PageLoad } from './$types';
export const load: PageLoad = async (event) => {
const { queryClient } = await event.parent();
const api = trpc(event, queryClient);
return {
streamed: { // Doesn't have to be named 'streamed'
queries: await api.createServerQueries((t) => [
t.authed.todos.all(),
t.public.hello.get()
])
}
};
} https://svelte.dev/blog/streaming-snapshots-sveltekit#stream-non-essential-data-in-load-functions I've never tried to do it with queries before and am honestly not really sure if it will work as intended. Sounds really interesting, though. I hope that this is helpful, give it a try and please update me if it works! |
Hmmm @vishalbalaji, wouldn't |
@dihmeetree You are right about the fact that you can't await queries, because the queries themselves are essentially stores and Svelte stores can only be created at the top level. However, one thing to note here is that This means that you should be able to achieve what you are trying to do by streaming the // src/routes/+page.ts
import { trpc } from "$lib/trpc/client";
import type { PageLoadEvent } from "./$types";
export async function load(event: PageLoadEvent) {
const { queryClient } = await event.parent();
const api = trpc(event, queryClient);
const utils = api.createUtils();
return {
// This query has to be disabled to prevent data being fetched again
// from the client side.
foo: () => api.greeting.createQuery('foo', { enabled: false }),
bar: await api.greeting.createServerQuery('bar'),
nested: {
foo: (async () => {
await new Promise((r) => setTimeout(r, 2000)); // delay to simulate a network response
await utils.greeting.prefetch('foo');
})(),
}
}
} <!-- src/routes/+page.svelte -->
<script lang="ts">
export let data;
const foo = data.foo();
const bar = data.bar();
</script>
<p>
{#if $bar.isPending}
Loading...
{:else if $bar.isError}
Error: {$bar.error.message}
{:else}
{$bar.data}
{/if}
</p>
{#await data.nested.foo}
<!-- -->
{/await}
<p>
{#if $foo.isPending}
Streaming...
{:else if $foo.isError}
Error: {$foo.error.message}
{:else}
{$foo.data}
{/if}
</p>
<button on:click={() => $foo.refetch()}>Refetch foo</button> I am demonstrating here using Here's a StackBlitz reproduction to see this in action: https://stackblitz.com/edit/stackblitz-starters-dvtn9s?file=src%2Froutes%2F%2Bpage.ts Maybe this could be simplified by creating a separate abstraction around this, called export async function load(event: PageLoadEvent) {
const { queryClient } = await event.parent();
const api = trpc(event, queryClient);
const [comments, prefetchComments] = createStreamedQuery(...);
return {
api,
comments,
nested: {
loadComments: await prefetchComments(),
}
}
} And in the template: <script lang="ts">
export let data;
const comments = data.comments();
</script>
{#await data.nested.loadComments}
Loading comments...
{:then}
<p>
{#if $comments.isLoading || $comments.isFetching}
Loading...
{:else if $comments.isError}
Error: {$comments.error.message}
{:else}
{$comments.data}
{/if}
</p>
<button on:click={() => $comments.refetch()}>Refetch comments</button>
{/await} Let me know if this is something that you see being useful in the library, but I think the above solution should be enough to resolve this issue. Anyway, thanks a lot for bringing this to my attention, this is quite interesting and I hadn't thought this far when I was initially working on this library. |
Hey @dihmeetree, closing this issue due to inactivity. Hope that your query was resolved. |
@vishalbalaji can we re-open this topic if you don't mind? I'm re-exploring TRPC and this package again. Apologies for not responding back to you. Regarding your last post..your example unfortunately doesn't do what I'm looking for. Streaming involves the use of So the server load function should return a promise of the query and then the frontend uses await to stream the data to the page. |
Hi @dihmeetree, I've been looking into this for the past couple of days now and found out that the client request seems to be occurring due to the tRPC request itself, as demonstrated with this example where I am trying to construct this scenario manually using the tRPC request and tanstack query: // src/routes/+page.ts
import { trpc } from "$lib/trpc/client";
import type { PageLoadEvent } from "./$types";
import { createQuery } from "@tanstack/svelte-query";
async function foo(event: PageLoadEvent) {
await new Promise((r) => setTimeout(r, 2000)); // delay to simulate a network response
// non-trpc request
// client request is not made
return 'foo';
// trpc-request
// client request is made
// const client = trpc(event)
// return client.greeting.query('foo');
}
export async function load(event: PageLoadEvent) {
const { queryClient } = await event.parent();
const fooQuery = { queryKey: ['foo'], queryFn: () => foo(event) };
return {
foo: () => createQuery(fooQuery),
streamedFoo: queryClient.prefetchQuery(fooQuery),
}
} <!-- src/routes/+page.svelte -->
<script lang="ts">
export let data;
const foo = data.foo();
</script>
<p>
{#await data.streamedFoo}
Streaming...
{:then}
{#if $foo.isPending || $foo.isLoading || $foo.isFetching}
Loading...
{:else if $foo.isError}
Error: {$foo.error.message}
{:else}
{$foo.data}
{/if}
{/await}
</p>
<button on:click={() => $foo.refetch()}>Refetch foo</button> Seems like it might be occurring due to the fact that the trpc call is happening on both client and server since we are calling it in Let me re-open this issue until I figure out a solution for this. |
I am |
Hey @natedunn, sorry for the long standing issue. I haven't been able to look into this issue much because I've been a bit busy with work and other things the past few weeks. From what I have seen though, the problem here seems to be an issue with For example, this doesn't work as expected: // +page.ts
import type { PageLoad } from "./$types";
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import type { Router } from "$lib/trpc/router";
export const load = (async (event) => {
const api = createTRPCProxyClient<Router>({
links: [httpBatchLink({
fetch: event.fetch,
url: `${event.url.origin}/trpc`,
})]
});
return {
waitFoo: (async () => {
await new Promise((r) => setTimeout(r, 2000));
return api.greeting.query('foo');
})()
};
}) satisfies PageLoad; <script lang="ts">
import type { PageData } from "./$types";
export let data: PageData;
</script>
{#await data.waitFoo}
Awaiting...
{:then foo}
{foo}
{/await} The client side request doesn't happen when doing the same with a regular API endpoint. If anyone has any insight into this, I would love to know why. |
Hey all, just thought that I would post an update to this issue since its been open for way longer that it should have been. I recently got some spare time to look into this a bit more and here's what I have gathered so far. First off, I came upon the realization that you can't really stream from a With this in mind, the steps to stream query data from the server become a bit simpler:
This, in implementation might look something like this: // $lib/trpc/router.ts
import type { Context } from '$lib/trpc/context';
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.context<Context>().create();
export const router = t.router({
getPosts: t.procedure.input(z.number()).query(async ({ input: id }) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
return res.json();
}),
getComments: t.procedure.input(z.number()).query(async ({ input: id }) => {
await new Promise((r) => setTimeout(r, 5000)); // 5 second simulated delay
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}/comments`);
return res.json();
})
});
export const createCaller = t.createCallerFactory(router);
export type Router = typeof router; // +page.server.ts
import { createContext } from '$lib/trpc/context';
import { createCaller } from '$lib/trpc/router';
export async function load(event) {
const api = createCaller(await createContext(event));
return {
comments: api.getComments(1),
post: await api.getPost(1)
};
} <!-- +page.svelte -->
<script lang="ts">
import { page } from '$app/stores';
import { trpc } from '$lib/trpc/client';
import type { PageData } from './$types';
export let data: PageData;
const queryClient = data.queryClient;
const api = trpc($page);
const post = api.getPost.createQuery(1, { initialData: data.post });
let enabled = false;
$: comments = api.getComments.createQuery(1, { enabled });
async function resolveQuery(key: QueryKey, promise: Promise<unknown>) {
queryClient.setQueryData(api.getComments.getQueryKey(1, 'query'), await promise);
enabled = true;
}
</script>
<h1>{$post.data?.title}</h1>
<p>{$post.data?.body}</p>
<h2>Comments</h2>
{#await resolveQuery(data.comments)}
Loading comments...
{:then}
{#each $comments.data as comment}
<p><b>{comment.email}</b>: {comment.body}</p>
{/each}
{/await} This in its raw form is a bit janky, especially if this needs to be repeated for multiple queries, but this can be done. Now, I was thinking of modifying the implementation to have an abstracted version of this in the library. However, I took a look at the updated docs for But if you want to have streamed queries in the meantime, the above example still works as expected. |
Instead of doing the following (to prerender queries on the server):
I'd like to be able to stream the result of
queries
for example on my page. I'm new to Svelte, but I know that Svelte does support streaming data via promises. Apparently if you don't await the data in the server load function, you can use theawait
block on the client to show a loading fallback while the promise resolves.For example (From: https://kit.svelte.dev/docs/load#streaming-with-promises):
In relation to
createServerQueries
, is there a way I can do streaming with it, so I can have faster page loads? Any advice/guidance would be super appreciated!The text was updated successfully, but these errors were encountered: