Froge
Froge16mo ago

'useInfiniteQuery' hook disappeared after moving to Turborepo

I am using Turborepo with Next.js with the following layout, originally a T3 app - /apps/app - /packages/api The useInfiniteQuery hook seems to have dissapeared after migrating, and I can't get it back. I am using @trpc/next on the clientside, @trpc/react-query is installed, and everything seems fine? I'd love some guidance, I'm a little stuck... /apps/app/src/utils/api.ts
import { httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import superjson from "superjson";

import { type AppRouter } from "@myapp/api";

const getBaseUrl = () => {
if (typeof window !== "undefined") return ""; // browser should use relative url
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};

config() {
return {
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" &&
opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
ssr: false,
});
import { httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import superjson from "superjson";

import { type AppRouter } from "@myapp/api";

const getBaseUrl = () => {
if (typeof window !== "undefined") return ""; // browser should use relative url
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};

config() {
return {
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" &&
opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
ssr: false,
});
/packages/api/src/routers/job.ts
findMany: publicProcedure
.input(
z.object({
where: JobPostWhereInputSchema.optional(),
take: z.number().min(1).max(100).optional(),
cursor: z.string().nullish(),
order: JobPostOrderByWithRelationInputSchema.optional(),
})
)
.query(...)
findMany: publicProcedure
.input(
z.object({
where: JobPostWhereInputSchema.optional(),
take: z.number().min(1).max(100).optional(),
cursor: z.string().nullish(),
order: JobPostOrderByWithRelationInputSchema.optional(),
})
)
.query(...)
`/packages/api/src/root.ts
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import superjson from "superjson";
import { job } from "./routers/job";
import { profile } from "./routers/profile";
import { createTRPCRouter } from "./trpc";

export const appRouter = createTRPCRouter({
profile,
job,
});

export type AppRouter = typeof appRouter;

export const client = createTRPCProxyClient<AppRouter>({
transformer: superjson,
links: [
httpBatchLink({
url: "http://localhost:3000/api/trpc",
}),
],
});
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import superjson from "superjson";
import { job } from "./routers/job";
import { profile } from "./routers/profile";
import { createTRPCRouter } from "./trpc";

export const appRouter = createTRPCRouter({
profile,
job,
});

export type AppRouter = typeof appRouter;

export const client = createTRPCProxyClient<AppRouter>({
transformer: superjson,
links: [
httpBatchLink({
url: "http://localhost:3000/api/trpc",
}),
],
});
26 Replies
Froge
Froge16mo ago
Froge
Froge16mo ago
Interesting - it might be something to do with the tsconfig I added strict: true through in the child tsconfigs even though it's enabled at a top level. And the job. disappeared from the router... Still investigating Okay, so turns out in my hate of setting up turborepo, strict mode wasn't enforced, that's enabled now. But what's odd, is the api.job is now never type 0_o And now finally at what appears to the be root of the issue
export const api = createTRPCNext<AppRouter>({
config() {
return {
/**
* Transformer used for data de-serialization from the server.
*
* @see https://trpc.io/docs/data-transformers
**/
transformer: superjson,

/**
* Links used to determine request flow from client to server.
*
* @see https://trpc.io/docs/links
* */
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" &&
opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
/**
* Whether tRPC should await queries when server rendering pages.
*
* @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
*/
ssr: false,
});
export const api = createTRPCNext<AppRouter>({
config() {
return {
/**
* Transformer used for data de-serialization from the server.
*
* @see https://trpc.io/docs/data-transformers
**/
transformer: superjson,

/**
* Links used to determine request flow from client to server.
*
* @see https://trpc.io/docs/links
* */
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" &&
opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
/**
* Whether tRPC should await queries when server rendering pages.
*
* @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
*/
ssr: false,
});
Throws:
Type 'CreateRouterInner<RootConfig<{ ctx: { req: NextApiRequest; res: NextApiResponse; session: Session | null; prisma: PrismaClient<PrismaClientOptions, never, RejectOnNotFound | ... 1 more ... | undefined>; }; meta: object; errorShape: { ...; }; transformer: typeof SuperJSON; }>, { ...; }>' does not satisfy the constraint 'Router<AnyRouterDef<AnyRootConfig, any>>'.
The types returned by 'createCaller(...)' are incompatible between these types.
Type '{ query: inferHandlerFn<{}>; mutation: inferHandlerFn<{}>; subscription: inferHandlerFn<{}>; } & DecoratedProcedureRecord<{ profile: CreateRouterInner<RootConfig<{ ctx: { req: NextApiRequest; res: NextApiResponse; session: Session | null; prisma: PrismaClient<...>; }; meta: object; errorShape: { ...; }; transformer:...' is not assignable to type '{ query: inferHandlerFn<any>; mutation: inferHandlerFn<any>; subscription: inferHandlerFn<any>; } & DecoratedProcedureRecord<any>'.
Type '{ query: inferHandlerFn<{}>; mutation: inferHandlerFn<{}>; subscription: inferHandlerFn<{}>; } & DecoratedProcedureRecord<{ profile: CreateRouterInner<RootConfig<{ ctx: { req: NextApiRequest; res: NextApiResponse; session: Session | null; prisma: PrismaClient<...>; }; meta: object; errorShape: { ...; }; transformer:...' is not assignable to type '{ query: inferHandlerFn<any>; mutation: inferHandlerFn<any>; subscription: inferHandlerFn<any>; }'.
Types of property 'query' are incompatible.
Type 'inferHandlerFn<{}>' is not assignable to type 'inferHandlerFn<any>'.
Types of parameters 'path' and 'path' are incompatible.
Type 'TPath' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.ts(2344)
Type 'CreateRouterInner<RootConfig<{ ctx: { req: NextApiRequest; res: NextApiResponse; session: Session | null; prisma: PrismaClient<PrismaClientOptions, never, RejectOnNotFound | ... 1 more ... | undefined>; }; meta: object; errorShape: { ...; }; transformer: typeof SuperJSON; }>, { ...; }>' does not satisfy the constraint 'Router<AnyRouterDef<AnyRootConfig, any>>'.
The types returned by 'createCaller(...)' are incompatible between these types.
Type '{ query: inferHandlerFn<{}>; mutation: inferHandlerFn<{}>; subscription: inferHandlerFn<{}>; } & DecoratedProcedureRecord<{ profile: CreateRouterInner<RootConfig<{ ctx: { req: NextApiRequest; res: NextApiResponse; session: Session | null; prisma: PrismaClient<...>; }; meta: object; errorShape: { ...; }; transformer:...' is not assignable to type '{ query: inferHandlerFn<any>; mutation: inferHandlerFn<any>; subscription: inferHandlerFn<any>; } & DecoratedProcedureRecord<any>'.
Type '{ query: inferHandlerFn<{}>; mutation: inferHandlerFn<{}>; subscription: inferHandlerFn<{}>; } & DecoratedProcedureRecord<{ profile: CreateRouterInner<RootConfig<{ ctx: { req: NextApiRequest; res: NextApiResponse; session: Session | null; prisma: PrismaClient<...>; }; meta: object; errorShape: { ...; }; transformer:...' is not assignable to type '{ query: inferHandlerFn<any>; mutation: inferHandlerFn<any>; subscription: inferHandlerFn<any>; }'.
Types of property 'query' are incompatible.
Type 'inferHandlerFn<{}>' is not assignable to type 'inferHandlerFn<any>'.
Types of parameters 'path' and 'path' are incompatible.
Type 'TPath' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.ts(2344)
Nick
Nick16mo ago
This tracks. The cursor needs to be there and if the parent object is nullable it doesn’t get detected. Strict null checks needs to be on at minimum and a new repo might not have that
Froge
Froge16mo ago
Yeah strict mode enabled through and through now. Was a red herring. Now having issues with the appRouter...
Nick
Nick16mo ago
An you share your backend setup?
Froge
Froge16mo ago
It's a next app, the appRouter and server are in their own package in a turborepo'd monorepo tRPC runs on the next app, not on an independent service
Froge
Froge16mo ago
Nick
Nick16mo ago
Right so the error actually starts in your AppRouter I think Ignore the frontend errors until that’s fixed
Froge
Froge16mo ago
100%
Nick
Nick16mo ago
What does the AppRouter look like? Might need to see quite a bit of the setup. Try commenting up sub routes until the error goes away
Froge
Froge16mo ago
That's the app router. Let me comment out the sub routes and see what happens.
import { company } from "./routers/company";
import { job } from "./routers/job";
import { profile } from "./routers/profile";
import { createTRPCRouter } from "./trpc";

/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
profile,
job,
company,
});

// export type definition of API
export type AppRouter = typeof appRouter;
import { company } from "./routers/company";
import { job } from "./routers/job";
import { profile } from "./routers/profile";
import { createTRPCRouter } from "./trpc";

/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
profile,
job,
company,
});

// export type definition of API
export type AppRouter = typeof appRouter;
So I've just changed it to this, removing my procedures and sub-routes. Same errors.
import { createTRPCRouter } from "./trpc";

import { z } from "zod";

import { protectedProcedure, publicProcedure } from "./trpc";

export const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),
getSecretMessage: protectedProcedure.query(() => {
return "you can now see this secret message!";
}),
});

/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
exampleRouter,
});

// export type definition of API
export type AppRouter = typeof appRouter;
import { createTRPCRouter } from "./trpc";

import { z } from "zod";

import { protectedProcedure, publicProcedure } from "./trpc";

export const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),
getSecretMessage: protectedProcedure.query(() => {
return "you can now see this secret message!";
}),
});

/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
exampleRouter,
});

// export type definition of API
export type AppRouter = typeof appRouter;
I'm happy to share the repo
Nick
Nick16mo ago
Can you show trpc.ts? Might be to do with t setup
Froge
Froge16mo ago
Froge
Froge16mo ago
Is pretty out of the box I think I've only added the admin middleware
Nick
Nick16mo ago
Oh discord doesn’t like text files on mobile 🫠 Best to paste a code block
Froge
Froge16mo ago
Sec I'll remove comments
import { prisma } from "@app/db";

import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import { type Session } from "next-auth";

import { getServerAuthSession } from "@app/auth";

export type CreateContextOptions = {
session: Session | null;
};

const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
prisma,
};
};

export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;

// Get the session from the server using the getServerSession wrapper function
const session = await getServerAuthSession({ req, res });

return {
...createInnerTRPCContext({
session,
}),
req,
res,
};
};

import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson";
import { ZodError } from "zod";

const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
});

export const createTRPCRouter = t.router;

export const publicProcedure = t.procedure;

/** Reusable middleware that enforces users are logged in before running the procedure. */
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});


export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);

const enfroceUserIsAdmin = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user.isAdmin) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});

export const adminProcedure = t.procedure.use(enfroceUserIsAdmin);
import { prisma } from "@app/db";

import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import { type Session } from "next-auth";

import { getServerAuthSession } from "@app/auth";

export type CreateContextOptions = {
session: Session | null;
};

const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
prisma,
};
};

export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;

// Get the session from the server using the getServerSession wrapper function
const session = await getServerAuthSession({ req, res });

return {
...createInnerTRPCContext({
session,
}),
req,
res,
};
};

import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson";
import { ZodError } from "zod";

const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
});

export const createTRPCRouter = t.router;

export const publicProcedure = t.procedure;

/** Reusable middleware that enforces users are logged in before running the procedure. */
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});


export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);

const enfroceUserIsAdmin = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user.isAdmin) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});

export const adminProcedure = t.procedure.use(enfroceUserIsAdmin);
Nick
Nick16mo ago
It’s definitely a weird one I don’t see anything obviously wrong but it’s hard to tell on mobile
Froge
Froge16mo ago
It's super weird... Is it a typescript or linting issue? As that's all I've been playing around with
Nick
Nick16mo ago
The images you shared with the errors are probably quite useful Do both projects have strict enabled?
Froge
Froge16mo ago
Everything has strict enabled
Nick
Nick16mo ago
Cool I’ll need to have another look a bit later when I’m on desktop
Froge
Froge16mo ago
I'll go through and even do it on a package level tsconfig, not just the base Thank you! Much appreciated! If you want the source code, I'm happy to share the repo with you
Nick
Nick16mo ago
Definitely make sure it’s not being switched off in any way. Base should be fine though The repo would probably help. If you have time try and put together a minimal reproduction
Froge
Froge16mo ago
You saw my antics before of chasing down tsconfig issues, so it could be, but I'm doubtful I'm based in AUS so it'll likely be tomorrow. I can share you the repo that it is now, if you shoot through your GH It's fairly minimal anyways, if I take it back any further I'll be stripping out the monorepo Hol' up - might have some version mismatching going on Wow, okay... so two packages had different version of @trpc/next and @trpc/server Must've happened when splitting out, I've done some more consolidating, brining the next client into the api package, wowweee Edit: And strict mode, obviously... shit breaks without it (but we know this, this is just for the others experiencing weirdness, if it's weird it's usually versions or strict mode)
Nick
Nick16mo ago
Brilliant, that was one thing I was going to check 😃
Froge
Froge16mo ago
Appreciate it @nlucas !