Angush
Angush2mo ago

Following documentation gives error: "You cannot dot into a client module from a server component."

Running: Node 21.6.2, tRPC 11.0.0-rc.553 (client, server, next, react-query), next 14.2.14, and react-query 5.59.0 So, I'm trying to follow the React-Query Server Components setup guide in the tRPC docs, and I've mostly just copy pasted the code out, yet my client component isn't working. This works:
import { trpcServer } from '@/trpc/server'
export const TestGreeting = async () => {
const message = await trpcServer.test()
if (!message) return <div>Loading...</div>
return <div>{message}</div>
}
import { trpcServer } from '@/trpc/server'
export const TestGreeting = async () => {
const message = await trpcServer.test()
if (!message) return <div>Loading...</div>
return <div>{message}</div>
}
But when I change it to a client component, like the code in the documentation on tRPC's website:
'use-client'

import { trpcClient } from '@/trpc/client'
export const TestGreeting = async () => {
const message = trpcClient.test.useQuery()
if (!message.data) return <div>Loading...</div>
return <div>{message.data}</div>
}
'use-client'

import { trpcClient } from '@/trpc/client'
export const TestGreeting = async () => {
const message = trpcClient.test.useQuery()
if (!message.data) return <div>Loading...</div>
return <div>{message.data}</div>
}
It spits out this error:
Unhandled Runtime Error
Error: Cannot access test.useQuery on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

Source
src/app/home/TestGreeting.tsx (7:35) @ useQuery
5 |
6 | export const TestGreeting = async () => {
> 7 | const message = trpcClient.test.useQuery()
| ^
Unhandled Runtime Error
Error: Cannot access test.useQuery on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

Source
src/app/home/TestGreeting.tsx (7:35) @ useQuery
5 |
6 | export const TestGreeting = async () => {
> 7 | const message = trpcClient.test.useQuery()
| ^
When using next dev --turbo rather than next dev, the error message is different, though pointing to the same line:
Error: Cannot read properties of undefined (reading 'useQuery')
Error: Cannot read properties of undefined (reading 'useQuery')
Correct me if I'm wrong, but... that's no longer a server component, right?
Solution:
ahh, finally found it. typo; 'use-client' rather than 'use client'.
Jump to solution
2 Replies
Angush
AngushOP2mo ago
The @/trpc/client.tsx file is also basically lifted wholesale from the documentation, except for renaming the export (and one import) and enabling superjson:
'use client'

import type { QueryClient } from '@tanstack/react-query'
import { QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import { createTRPCReact } from '@trpc/react-query'
import { useState } from 'react'
import { makeQueryClient } from './query-client'
import type { AppRouter } from './routes'
import superjson from 'superjson'

export const trpcClient = createTRPCReact<AppRouter>()

let clientQueryClientSingleton: QueryClient
function getQueryClient() {
if (typeof window === 'undefined') return makeQueryClient()
return (clientQueryClientSingleton ??= makeQueryClient())
}
function getUrl() {
const base = (() => {
if (typeof window !== 'undefined') return ''
return 'http://localhost:3000'
})()
return `${base}/api/trpc`
}
export function TRPCProvider(props: Readonly<{ children: React.ReactNode }>) {
const queryClient = getQueryClient()
const [trpc] = useState(() =>
trpcClient.createClient({
links: [
httpBatchLink({
transformer: superjson,
url: getUrl(),
}),
],
})
)
return (
<trpcClient.Provider client={trpc} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
</trpcClient.Provider>
)
}
'use client'

import type { QueryClient } from '@tanstack/react-query'
import { QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import { createTRPCReact } from '@trpc/react-query'
import { useState } from 'react'
import { makeQueryClient } from './query-client'
import type { AppRouter } from './routes'
import superjson from 'superjson'

export const trpcClient = createTRPCReact<AppRouter>()

let clientQueryClientSingleton: QueryClient
function getQueryClient() {
if (typeof window === 'undefined') return makeQueryClient()
return (clientQueryClientSingleton ??= makeQueryClient())
}
function getUrl() {
const base = (() => {
if (typeof window !== 'undefined') return ''
return 'http://localhost:3000'
})()
return `${base}/api/trpc`
}
export function TRPCProvider(props: Readonly<{ children: React.ReactNode }>) {
const queryClient = getQueryClient()
const [trpc] = useState(() =>
trpcClient.createClient({
links: [
httpBatchLink({
transformer: superjson,
url: getUrl(),
}),
],
})
)
return (
<trpcClient.Provider client={trpc} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
</trpcClient.Provider>
)
}
Every other file involved has also not been changed much from the documentation, if at all. I did notice while I was going through the guide that it instructs you to create this trpc/init.ts file to intialize the tRPC backend:
import { initTRPC } from '@trpc/server';
import { cache } from 'react';
export const createTRPCContext = cache(async () => {
return { userId: 'user_123' };
});
const t = initTRPC.create({
// transformer: superjson,
});
export const createTRPCRouter = t.router;
export const baseProcedure = t.procedure;
import { initTRPC } from '@trpc/server';
import { cache } from 'react';
export const createTRPCContext = cache(async () => {
return { userId: 'user_123' };
});
const t = initTRPC.create({
// transformer: superjson,
});
export const createTRPCRouter = t.router;
export const baseProcedure = t.procedure;
But when it's instructing you to create the trpc/server.tsx file with this code:
import 'server-only';
import { createHydrationHelpers } from '@trpc/react-query/rsc';
import { cache } from 'react';
import { createCallerFactory, createTRPCContext } from './init';
import { makeQueryClient } from './query-client';
import { appRouter } from './routers/_app';

export const getQueryClient = cache(makeQueryClient);
const caller = createCallerFactory(appRouter)(createTRPCContext);
export const { trpc, HydrateClient } = createHydrationHelpers<typeof appRouter>(
caller,
getQueryClient,
);
import 'server-only';
import { createHydrationHelpers } from '@trpc/react-query/rsc';
import { cache } from 'react';
import { createCallerFactory, createTRPCContext } from './init';
import { makeQueryClient } from './query-client';
import { appRouter } from './routers/_app';

export const getQueryClient = cache(makeQueryClient);
const caller = createCallerFactory(appRouter)(createTRPCContext);
export const { trpc, HydrateClient } = createHydrationHelpers<typeof appRouter>(
caller,
getQueryClient,
);
It imports createCallerFactory from that previous init file, despite never telling you to make that an export. So I took a guess and added this to the end: export const createCallerFactory = t.createCallerFactory But I'm wondering, if the documentation had that error, does it have others? Or am I an idiot and this is this just user error, with something I'm overlooking? I'm happy to share any other files needed.
Solution
Angush
Angush2mo ago
ahh, finally found it. typo; 'use-client' rather than 'use client'.