Can I combo Tanstack query and Server component on Nextjs?

I was using Tanstack query for server state management with Nextjs, But I was concerned that almost all my components are getting 'use client ' so Is therr any design pattern or way to benefit both world ?
20 Replies
Gary, el Pingüino Artefacto
I do it, we prefetch the queries on the server and the hydrate them on the client to achive pooling/realtime data We also try to put "use client" only on the "leaves" components
jalen21
jalen21OP4w ago
@Gary, el Pingüino Artefacto Do you have example repo please? @Gary, el Pingüino Artefacto Is initialData good approach? fetch initially on server component and pass it to client component, then inside the client component pass the initialData to queryClient ? in which the queryClient is a custom hook for data fetching
Gary, el Pingüino Artefacto
i prefer this
No description
Gary, el Pingüino Artefacto
initialData makes prop drilling
jalen21
jalen21OP4w ago
@Gary, el Pingüino Artefacto Okay, so I have to use the hydration boundary on every page ?
Gary, el Pingüino Artefacto
yes It's kinda annoying but I prefer it than passing props
jalen21
jalen21OP4w ago
Does the prefetch streaming approach send html to browser or the data it self?
Gary, el Pingüino Artefacto
i haven't try it, i think it just send the promises and resolve it on the client so only the data is sent
jalen21
jalen21OP4w ago
oh but If I used the initial data approach, it will send the html, right? @Gary, el Pingüino Artefacto Here is what I know so far: - if I used the server component to fetch data it will send html so the network tab ( browser ) doesn't show the data structure, it will be RSC payload - But when I used the tanstack query to fetch data, on the network tab, the payload is showing the data structure on the response Am I correct ?
Gary, el Pingüino Artefacto
the prefetch/hydrate and the initial data di exactly the same thing
jalen21
jalen21OP4w ago
Oh so the payload is RSC like
jalen21
jalen21OP4w ago
great, thanks
Gary, el Pingüino Artefacto
you will still need the "use client" directive when using react query
jalen21
jalen21OP4w ago
But If I don't use the prefetch + hydrate, it will not be RSC payload, right ? yeah, I know
Gary, el Pingüino Artefacto
it will still be ssr if thats what you are asking
jalen21
jalen21OP4w ago
Hmm, I don't understand This is what my provider looks like
"use client";

import React, { useState } from "react";
import { defaultShouldDehydrateQuery, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";





const ReactQueryDevtoolsProduction = React.lazy(() =>
import("@tanstack/react-query-devtools/build/modern/production.js").then(
(d) => ({
default: d.ReactQueryDevtools,
})
)
)

export default function Providers({ children }: { children: React.ReactNode }) {
const [showDevtools, setShowDevtools] = React.useState(false)

React.useEffect(() => {
// @ts-expect-error
window.toggleDevtools = () => setShowDevtools((old) => !old)
}, [])

const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
// staleTime: 0,
// refetchInterval: 24 * 60 * 60 * 1000,
},
dehydrate: {
// per default, only successful Queries are included,
// this includes pending Queries as well
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === "pending",
},
},
})
)

return (
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
{showDevtools && (
<React.Suspense fallback={null}>
<ReactQueryDevtoolsProduction />
</React.Suspense>
)}
{children}
</QueryClientProvider>
)
}
"use client";

import React, { useState } from "react";
import { defaultShouldDehydrateQuery, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";





const ReactQueryDevtoolsProduction = React.lazy(() =>
import("@tanstack/react-query-devtools/build/modern/production.js").then(
(d) => ({
default: d.ReactQueryDevtools,
})
)
)

export default function Providers({ children }: { children: React.ReactNode }) {
const [showDevtools, setShowDevtools] = React.useState(false)

React.useEffect(() => {
// @ts-expect-error
window.toggleDevtools = () => setShowDevtools((old) => !old)
}, [])

const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
// staleTime: 0,
// refetchInterval: 24 * 60 * 60 * 1000,
},
dehydrate: {
// per default, only successful Queries are included,
// this includes pending Queries as well
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === "pending",
},
},
})
)

return (
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
{showDevtools && (
<React.Suspense fallback={null}>
<ReactQueryDevtoolsProduction />
</React.Suspense>
)}
{children}
</QueryClientProvider>
)
}
I think I should have used HydartionBoundary to benefit this approach, in which I'm not using currently
Gary, el Pingüino Artefacto
this is mine
"use client"

import type { ReactNode } from "react"
import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"

let browserQueryClient: QueryClient | undefined = undefined

function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60,
gcTime: 1000 * 60 * 60 * 24,
},
mutations: {
onSuccess: async () => {
await browserQueryClient?.invalidateQueries()
},
},
},
})
}

function getQueryClient() {
if (isServer) {
return makeQueryClient()
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}

export function ReactQueryClientProvider(props: { children: ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{props.children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}
"use client"

import type { ReactNode } from "react"
import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"

let browserQueryClient: QueryClient | undefined = undefined

function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60,
gcTime: 1000 * 60 * 60 * 24,
},
mutations: {
onSuccess: async () => {
await browserQueryClient?.invalidateQueries()
},
},
},
})
}

function getQueryClient() {
if (isServer) {
return makeQueryClient()
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}

export function ReactQueryClientProvider(props: { children: ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{props.children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}
i called makeQueryClient on the server and use a HydrationBoundary with the prefetch thats enought to get ssr working
jalen21
jalen21OP4w ago
@Gary, el Pingüino Artefacto Then, I use this on pages ( HydrationBoundary ) BTW, what's the core difference ( your implementation from mine )
Want results from more Discord servers?
Add your server