Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tanstack hydrating order, Jotai first, or Tanstack first? #37

Open
davit-b opened this issue Aug 24, 2023 · 4 comments
Open

Tanstack hydrating order, Jotai first, or Tanstack first? #37

davit-b opened this issue Aug 24, 2023 · 4 comments

Comments

@davit-b
Copy link

davit-b commented Aug 24, 2023

I've created a React SPA using Next13 appDir, Jotai, and Tanstack.

Question: What order do I hydrate in?

Option A:

  1. Initial datafetch in server component
  2. pass as props to a client component
  3. ...which hydrates a Jotai atom using useHydrateAtoms
  4. the atomsWithQuery automatically get's hydrated with initialData
// atoms.ts
export const dataAtom = atom<DataType | null>(null)
export const queryAtom = atomsWithQuery(get => ({
  queryKey: QUERY_KEY,
  queryFn: fetchingFunction,
  initialData: get(dataAtom)
}))
//--------------------------------

// app/page.tsx [SERVER]
export default async function Page() {
  const data = getUser()

  return (
    <div>
      <HydratorComponent initialDataSentFromServer={data}/>
      <RestOfTheApp/>
    </div>
  )
}

// /components/HydratorComponent
'use client'
import React from 'react'

function HydratorComponent({initialDataSentFromServer}) {
  useHydrateAtoms([[dataAtom, initialDataSentFromServer]])
  return (
    <div></div>
  )
}

TL;DR: SERVER (fetch) -> CLIENT (as props) -> useHydrateAtom (a basic data atom) -> Query Atom gets populate with initialData

-- or --

Option B:

  1. define a queryAtom without initialData
  2. initial data fetch on server component
  3. pass the initial data as props to client component
  4. ...which hydrates the query atom using useHydrateAtoms([[dataAtom, initialDataSentFromServer]])
// atoms.ts
export const queryAtom = atomsWithQuery((get) => ({
  queryKey: QUERY_KEY,
  queryFn: fetchingFunction,
}))
//--------------------------------

// app/page.tsx [SERVER]
export default async function Page() {
  const data = getUser()

  return (
    <div>
      <HydratorComponent initialDataSentFromServer={data} />
      <RestOfTheApp />
    </div>
  )
}

// /components/HydratorComponent
'use client'
import React from 'react'

function HydratorComponent({ initialDataSentFromServer }) {
  useHydrateAtoms([[queryAtom, initialDataSentFromServer]])
  return <div></div>
}

TL;DR: SERVER (fetch) -> CLIENT (as props) -> useHydrateAtom (the queryAtom)

I'm a beginner and I really like Jotai. Would love to figure out the best practice for this.

@dai-shi
Copy link
Member

dai-shi commented Aug 25, 2023

Can anyone help here?

I never tried, but I don't think atoms created by jotai-tanstack-query should be hydrated on the Jotai end.

@estubmo
Copy link

estubmo commented Aug 25, 2023

This is my setup:

providers.tsx

"use client";

import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
import { Provider } from "jotai";

import HydrateQueryClientAtom from "~/components/HydrateQueryClientAtom"

export default function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = React.useState(() => new QueryClient());

  return (
      <QueryClientProvider client={queryClient}>
        <ReactQueryStreamedHydration>
          <Provider>
            <HydrateQueryClientAtom queryClient={queryClient}>
              {children}
            </HydrateQueryClientAtom>
          </Provider>
        </ReactQueryStreamedHydration>
        <ReactQueryDevtools initialIsOpen={true} />
      </QueryClientProvider>
  );
}

Providers wrap my entire app in the root layout.tsx.

HydrateQueryClientAtom.tsx

"use client";

import type { QueryClient } from "@tanstack/query-core";
import { queryClientAtom } from "jotai-tanstack-query";
import { useHydrateAtoms } from "jotai/utils";

const HydrateQueryClientAtom = ({ children, queryClient }: { children: React.ReactNode; queryClient: QueryClient }) => {
  useHydrateAtoms([[queryClientAtom, queryClient]]);

  return children;
};

export default HydrateQueryClientAtom;

And then in any page wrapped by this layout I can go something like:

import { dehydrate } from "@tanstack/query-core";
import { HydrationBoundary } from "@tanstack/react-query";

async function preFetchSomeData() {
  const queryClient = getQueryClient();
  await queryClient.prefetchQuery({ queryKey: ["key", params], queryFn: dataQueryFunction });
  const dehydratedState = dehydrate(queryClient);

  return dehydratedState
}

export default async function SomePage() {
  const dehydratedState = await preFetchSomeData()

  return (
    <HydrationBoundary state={dehydratedState}>
      // ...
    </HydrationBoundary>
  );
}

I hope that answers your questions.

@davit-b
Copy link
Author

davit-b commented Aug 25, 2023

@estubmo
Extremely helpful.

Similar to my Tanstack-only setup right now.

export default async function HydratedApp() {
  const data = await getDataServerSide() // server-side data fetching function

  const queryClient = getQueryClient()
  queryClient.setQueryData(['QUERY_KEY'], data)
  const dehydratedState = dehydrate(queryClient)

  return (
      <Hydrate state={dehydratedState}>
        <App /> // The `use client` wrapped SPA in inside of <App />
      </Hydrate>
  )
}
// Inside a component deep in the tree.
const { data: userData } = useQuery({
  queryKey: ['QUERY_KEY'],
  queryFn: getDataClientSide //client-side datafetching function
})

BUT with Tanstack-only I have to access data client-side using Tanstack's convention, and my React components are subscribed using Tanstack's hooks — and I can't use the magic of Jotai for deriving atoms or anything off that data.


Before I ask any followups, I'm going to experiment with this setup and report my findings.
Thank you!

@estubmo
Copy link

estubmo commented Aug 25, 2023

You're very welcome. I updated the example, as it was missing an essential part, calling queryClient.prefetch().
I think that might be a more suitable API than setQueryData.
https://tanstack.com/query/v4/docs/react/reference/QueryClient#queryclientprefetchquery

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants