Reputation: 11
It still invalidates the cache on the server side(so on forced refresh it is shown), but doesn't invalidate the cache on my ssr rendered pages/layouts. This causes my users to be stuck and see stale data this persists for SSR rendered pages that don't use tRPC, and for when using them with a query from tRPC that is being manually revalidated with tRPC utils.
I tried using tRPC 11, also tried moving to 10.45 (latest stable) and it persists. I tried workarounds, couldn't find a way to make this happen What am I missing? Using these packages:
"@trpc/client": "^11.0.0-rc.446",
"@trpc/react-query": "^11.0.0-rc.446",
"@trpc/server": "^11.0.0-rc.446",
"@tanstack/react-query": "^5.50.0",
and this code snippet taken out of my codebase: https://github.com/yehonatanyosefi/trpctest
Revalidating client side cache when using a tRPC route for a mutation (along with invalidating the mutations that go alongside it).
Expectation: showing new data when navigating to old layouts/pages that used this cache.
Actual: showing stale data until refresh.
The "revalidatePath('/pathname')" isn't working on my tRPC setup, does anyone know why? It is working for the server, but the client shows the cache until a force refresh.
trpc files:
client/query-client.ts:
import { defaultShouldDehydrateQuery, QueryClient } from '@tanstack/react-query'
import SuperJSON from 'superjson'
export const createQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 0,
gcTime: 0,
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchOnReconnect: true,
},
dehydrate: {
serializeData: SuperJSON.serialize,
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) || query.state.status === 'pending',
},
hydrate: {
deserializeData: SuperJSON.deserialize,
},
},
})
client/react.tsx:
'use client'
import { createQueryClient } from '@/trpc/client/query-client'
import type { AppRouter } from '@/trpc/server/root'
import { toSiteURL } from '@/utils/helpers'
import type { QueryClient } from '@tanstack/react-query'
import { QueryClientProvider } from '@tanstack/react-query'
import { loggerLink, unstable_httpBatchStreamLink } from '@trpc/client'
import { createTRPCReact } from '@trpc/react-query'
import { useState } from 'react'
import SuperJSON from 'superjson'
let clientQueryClientSingleton: QueryClient | undefined = undefined
const getQueryClient = () => {
if (typeof window === 'undefined') {
// Server: always make a new query client
return createQueryClient()
} else {
// Browser: use singleton pattern to keep the same query client
return (clientQueryClientSingleton ??= createQueryClient())
}
}
export const api = createTRPCReact<AppRouter>()
export function TRPCReactProvider(props: { children: React.ReactNode }) {
const queryClient = getQueryClient()
const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
transformer: SuperJSON,
url: toSiteURL('/api/trpc'),
headers() {
const headers = new Headers()
headers.set('x-trpc-source', 'nextjs-react')
return headers
},
}),
],
}),
)
return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</api.Provider>
</QueryClientProvider>
)
}
client/server.ts:
import { createQueryClient } from '@/trpc/client/query-client'
import { createCaller, createTRPCContext } from '@/trpc/server/index'
import type { AppRouter } from '@/trpc/server/root'
import { createHydrationHelpers } from '@trpc/react-query/rsc'
import { headers } from 'next/headers'
import { cache } from 'react'
/**
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
* handling a tRPC call from a React Server Component.
*/
const createContext = cache(async () => {
const heads = new Headers(headers())
heads.set('x-trpc-source', 'rsc')
return createTRPCContext({
headers: heads,
})
})
const getQueryClient = cache(createQueryClient)
const caller = createCaller(createContext)
export const { trpc: api, HydrateClient } = createHydrationHelpers<AppRouter>(
caller,
getQueryClient
)
server/index.ts:
import type { AppRouter } from '@/trpc/server/root'
import { appRouter } from '@/trpc/server/root'
import { createCallerFactory, createTRPCContext } from '@/trpc/server/trpc'
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server'
/**
* Create a server-side caller for the tRPC API
* @example
* const trpc = createCaller(createContext);
* const res = await trpc.post.all();
* ^? Post[]
*/
const createCaller = createCallerFactory(appRouter)
/**
* Inference helpers for input types
* @example
* type PostByIdInput = RouterInputs['post']['byId']
* ^? { id: number }
**/
type RouterInputs = inferRouterInputs<AppRouter>
/**
* Inference helpers for output types
* @example
* type AllPostsOutput = RouterOutputs['post']['all']
* ^? Post[]
**/
type RouterOutputs = inferRouterOutputs<AppRouter>
export { appRouter, createCaller, createTRPCContext }
export type { AppRouter, RouterInputs, RouterOutputs }
server/root.ts:
import { createTRPCRouter } from '@/trpc/server/trpc'
//other routes..
import { userRouter } from '@/packages/user/features/user/user.router'
export const appRouter = createTRPCRouter({
//other routes...
user: userRouter,
})
// export type definition of API
export type AppRouter = typeof appRouter
server/trpc.ts:
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { ZodError } from 'zod'
export const createTRPCContext = async (opts: { headers: Headers }) => {
const authToken = opts.headers.get('Authorization') ?? null
const source = opts.headers.get('x-trpc-source') ?? 'unknown'
console.log('>>> tRPC Request from', source)
return {
token: authToken,
}
}
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: {
...superjson,
serialize: (obj) => {
if (typeof obj === 'function') {
return {
__type: 'function',
__value: obj.toString(),
}
}
return superjson.serialize(obj)
},
deserialize: (obj) => {
if (
obj &&
typeof obj === 'object' &&
'__type' in obj &&
obj.__type === 'function'
) {
return new Function('return ' + obj.__value)()
}
return superjson.deserialize(obj)
},
},
errorFormatter: ({ shape, error }) => ({
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
}),
})
/**
* Create a server-side caller
* @see https://trpc.io/docs/server/server-side-calls
*/
export const createCallerFactory = t.createCallerFactory
/**
* This is how you create new routers and subrouters in your tRPC API
* @see https://trpc.io/docs/router
*/
export const createTRPCRouter = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure
Using the api.user.someQueryFunction()
to trigger the server and api.user.someMutateFunction.useMutation()
to make the mutation.
Revalidating the query for the function manually and not revalidating, using the server side rendered function directly or using tRPC does not change stuff, only the mutation matters as the revalidatePath
isn't working.
Upvotes: 1
Views: 267