Skip to content
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

[Discussion] using Apollo Client's queryPreloader in TanStack router loaders #1675

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/react/basic-ssr-streaming-file-based/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
"debug": "node --inspect-brk server"
},
"dependencies": {
"@apollo/client": "^3.10.4",
"@tanstack/react-router": "^1.34.3",
"@tanstack/start": "^1.34.3",
"@tanstack/router-devtools": "^1.34.4",
"@tanstack/router-vite-plugin": "^1.34.1",
"redaxios": "^0.5.0",
"@tanstack/start": "^1.34.3",
"get-port": "^7.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"redaxios": "^0.5.0",
"superjson": "^2.2.1"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions examples/react/basic-ssr-streaming-file-based/src/apollo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ApolloClient, InMemoryCache } from '@apollo/client'

export function makeClient() {
const client = new ApolloClient({
uri: 'https://flyby-router-demo.herokuapp.com/',
cache: new InMemoryCache(),
})
return client
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export async function render(opts: {
router.update({
history: memoryHistory,
context: {
// is this the official way to do this?
...router.options.context,
head: opts.head,
},
})
Expand Down
15 changes: 14 additions & 1 deletion examples/react/basic-ssr-streaming-file-based/src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { createRouter as createReactRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'
import SuperJSON from 'superjson'
import { ApolloProvider, createQueryPreloader } from '@apollo/client'
import { routeTree } from './routeTree.gen'
import { makeClient } from './apollo'

export function createRouter() {
const apolloClient = makeClient()
return createReactRouter({
routeTree,
context: {
head: '',
preloadQuery: createQueryPreloader(apolloClient),
apolloClient,
},
Wrap({ children }) {
return <ApolloProvider client={apolloClient}>{children}</ApolloProvider>
},
defaultPreload: 'intent',
// Maybe this is already possible if we provided some kind of
// `transformer: apolloTransformer(client)`
// here.
// Can a transformer return Promises?
// Maybe even streams or async iterables?
transformer: SuperJSON,
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import type { ApolloClient, PreloadQueryFunction } from '@apollo/client'
export type RouterContext = {
head: string
preloadQuery: PreloadQueryFunction
// userland code doesn't need this, but maybe it would
// be good to make `context` available to the deserialization
// process, so I'm already putting this here
apolloClient: ApolloClient<any>
}
88 changes: 62 additions & 26 deletions examples/react/basic-ssr-streaming-file-based/src/routes/posts.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,83 @@
import * as React from 'react'
import { createFileRoute, Link, Outlet } from '@tanstack/react-router'
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
import { gql, useReadQuery } from '@apollo/client'
import type { TypedDocumentNode } from '@apollo/client'

export type PostType = {
id: string
title: string
body: string
}

const GET_LOCATIONS: TypedDocumentNode<{
locations: Array<{
id: string
name: string
description: string
photo: string
}>
}> = gql`
query GetLocations {
locations {
id
name
description
photo
}
}
`

export const Route = createFileRoute('/posts')({
loader: async () => {
console.log('Fetching posts...')
await new Promise((r) =>
setTimeout(r, 300 + Math.round(Math.random() * 300)),
)
return fetch('https://jsonplaceholder.typicode.com/posts')
.then((d) => d.json() as Promise<PostType[]>)
.then((d) => d.slice(0, 10))
loader: async ({ context: { preloadQuery } }) => {
console.log('Fetching locations...')
return {
/**
* This creates a `QueryRef` object.
* This object is not serializable by default,
* so we'd love to be able to hook into the
* serialization process to provide our own serialization.
*
* The serialized value will contain static values and a Promise,
* although an AsyncIterator or some kind of Stream would be
* preferrable.
*/
locationsRef: preloadQuery(GET_LOCATIONS),
}
},
component: PostsComponent,
})

function PostsComponent() {
const posts = Route.useLoaderData()
/**
* `locationsRef` needs to be deserialized here.
* But it doesn't only need to be deserialized:
* The deserialization also has a side effect.
*
* The QueryRef needs to be registered with the ApolloClient.
*
*/
const { locationsRef } = Route.useLoaderData()

const { data } = useReadQuery(locationsRef)

return (
<div className="p-2 flex gap-2">
<ul className="list-disc pl-4">
{posts?.map((post) => {
return (
<li key={post.id} className="whitespace-nowrap">
<Link
to="/posts/$postId"
params={{
postId: post.id,
}}
className="block py-1 text-blue-800 hover:text-blue-600"
activeProps={{ className: 'text-black font-bold' }}
>
<div>{post.title.substring(0, 20)}</div>
</Link>
</li>
)
})}
{data.locations.map(({ id, name, description, photo }) => (
<div key={id}>
<h3>{name}</h3>
<img
width="400"
height="250"
alt="location-reference"
src={`${photo}`}
/>
<br />
<b>About this location:</b>
<p>{description}</p>
<br />
</div>
))}
<li className="whitespace-nowrap">
<Link
to="/posts/$postId"
Expand Down
Loading