Spoeky
Spoeky10mo ago

TRPC with Nextjs 13 App Router

Im trying to implement TRPC with my already existing Nextjs 13 App to get typesafe API's. I've managed to get it to work in Client Components, meaning i can make the API Calls from them but whenever I try to use them on a server rendered Page, I keep getting an error. In the tutorials I followed, it worked perfectly fine in the Server Rendered Page so I'm wondering how to fix this. Related Files:
layout.tsx:

import "./globals.css";
import { Inter } from "next/font/google";
import { ThemeProvider } from "@/src/app/components/providers/ThemeProvider";
import Providers from "./_trpc/Providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
title: "",
description: "",
};

function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<Providers>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="light">
{children}
</ThemeProvider>
</body>
</Providers>
</html>
);
}

export default RootLayout;

-------
client.ts

import { AppRouter } from "@/src/server";
import { createTRPCReact } from "@trpc/react-query";

export const trpc = createTRPCReact<AppRouter>({});

--------
Providers.tsx

"use client";

import { trpc } from "@/src/app/_trpc/client";
import { absoluteUrl } from "@/lib/utils";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { PropsWithChildren, useState } from "react";

const Providers = ({ children }: PropsWithChildren) => {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: absoluteUrl("/api/trpc"),
}),
],
})
);

return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</trpc.Provider>
);
};

export default Providers;


-----
page.tsx


import PostList from "./components/ui/PostList";
import { getCurrentUser } from "@/lib/session";
import Navbar from "./components/sections/Navbar";
import { fetchPosts } from "@/lib/actions/post.actions";
import BottomNavbar from "./components/sections/BottomNavbar";
import Temp from "./components/Temp";
import { trpc } from "./_trpc/client";

const Home = async () => {
const user = await getCurrentUser();

const posts = await fetchPosts();

const { data } = trpc.posts.getMany.useQuery();

return (
<div>
<Navbar user={user} />
<Temp />
<div className="px-4 pt-2 mx-auto max-w-[1245px]">
<PostList posts={posts} userId={user?.id} />
</div>
<BottomNavbar user={user} />
</div>
);
};

export default Home;
layout.tsx:

import "./globals.css";
import { Inter } from "next/font/google";
import { ThemeProvider } from "@/src/app/components/providers/ThemeProvider";
import Providers from "./_trpc/Providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
title: "",
description: "",
};

function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<Providers>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="light">
{children}
</ThemeProvider>
</body>
</Providers>
</html>
);
}

export default RootLayout;

-------
client.ts

import { AppRouter } from "@/src/server";
import { createTRPCReact } from "@trpc/react-query";

export const trpc = createTRPCReact<AppRouter>({});

--------
Providers.tsx

"use client";

import { trpc } from "@/src/app/_trpc/client";
import { absoluteUrl } from "@/lib/utils";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { PropsWithChildren, useState } from "react";

const Providers = ({ children }: PropsWithChildren) => {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: absoluteUrl("/api/trpc"),
}),
],
})
);

return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</trpc.Provider>
);
};

export default Providers;


-----
page.tsx


import PostList from "./components/ui/PostList";
import { getCurrentUser } from "@/lib/session";
import Navbar from "./components/sections/Navbar";
import { fetchPosts } from "@/lib/actions/post.actions";
import BottomNavbar from "./components/sections/BottomNavbar";
import Temp from "./components/Temp";
import { trpc } from "./_trpc/client";

const Home = async () => {
const user = await getCurrentUser();

const posts = await fetchPosts();

const { data } = trpc.posts.getMany.useQuery();

return (
<div>
<Navbar user={user} />
<Temp />
<div className="px-4 pt-2 mx-auto max-w-[1245px]">
<PostList posts={posts} userId={user?.id} />
</div>
<BottomNavbar user={user} />
</div>
);
};

export default Home;
Here is the Error i get: You're importing a component that needs useEffect. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default. ╭─[@trpc+react-query@10.43.0@tanstack+react-query@4.36.1@trpc+client@10.43.0_@trpc+server@10.4_7lsmxlrq3xi3juoktsh4534bt4\node_modules@trpc\react-query\dist\createHooksInternal-bdff7171.mjs:2:1] 4 │ import { useQuery, useQueryClient, useMutation, hashQueryKey, useInfiniteQuery, useQueries } from '@tanstack/react-query'; 5 │ import React, { createContext, useRef, useState, useEffect, useCallback, useMemo } from 'react'; · ───────── 6 │ 7 │ /** 8 │ * We treat undefined as an input the same as omitting an input ╰──── Maybe one of these should be marked as a client entry with "use client": ./src\app_trpc\client.ts ./src\app\page.tsx
1 Reply
p6l.richard
p6l.richard9mo ago
Without knowing the context, my assumption is that you're using the api exported from app_trpc/client.ts in the app/page.tsx, which is a page without the use client directive. If you're using a mutation in a form (or similar), simply move that form into it's own component (separate from the page.tsx) and add "use client" in the first line of the file. Then import your NewForm component from page.tsx and this error should go away.