preloadQuery with Tanstack Router loader

I am attempting to use preloadQuery with the Tanstack router loader function

  loader: ({ params }) => {
    return preloadQuery(DealQuery, {
      variables: { dealId: params.dealId },
    });
  },

Then in the component

  const dealQueryRef = Route.useLoaderData();
  const { data } = useReadQuery(dealQueryRef);

Every other time I click on a link it will give me

Error message
Expected a QueryRef object, but got something else instead.

I’m not entirely sure why it’s working sometimes and not others. My <Outlet /> to this route is wrapped in a <Suspense>

Hey @BrennenRocks :waving_hand:

We had a similar reported issue last week (Using toPromise in a tanstack router loader loses internal queryRef · Issue #12619 · apollographql/apollo-client · GitHub). Are you using TanStack router with our TanStack integration or with just the plain @apollo/client package? If just the plain package, would you mind trying to TanStack integration and seeing if that helps here?

This is the exact issue I’m facing. I’m not using TanStack Start, just TanStack Router with a Vite React App. I will give it a try

I don’t think there is anything too specific to TanStack Start, but happy to be wrong! That package is still in beta (we plan to release it publicly soon once we get more feedback) so please let me know if you do run into issues with that!

Do you know if there is an example of someone using this library?

Not to my knowledge, but @lenz might know more (he’s been the one primarily working on that library integration).

Would a workaround to using the preloadQuery be to just call the client directly in the loader?
client.query({ query: DealQuery, variables: { dealId: params.dealId } });

To at least populate the cache?

That might work! I’ll be honest I haven’t worked much with TanStack Router so I don’t entirely know the ins and outs, but worth a try!

For what it’s worth I did give apollo-client-integrations/packages/tanstack-start at main · apollographql/apollo-client-integrations · GitHub a try but to no avail. Here is my implementation if @lenz wanted to give it a glance.

client.ts

import { createQueryPreloader } from '@apollo/client';
import { ApolloClient, InMemoryCache } from '@apollo/client-integration-tanstack-start';

export const client = new ApolloClient({
  cache: new InMemoryCache(),
});

export const preloadQuery = createQueryPreloader(client);

router.ts

import { createRouter as createTanStackRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';
import { routerWithApolloClient } from '@apollo/client-integration-tanstack-start';
import { createQueryPreloader } from '@apollo/client';
import { client } from './providers/le-apollo-provider/client';

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
    defaultPreload: 'intent',
    scrollRestoration: true,
    defaultStructuralSharing: true,
    // Let Apollo handle caching in loaders instead of Tanstack Router https://tanstack.com/router/v1/docs/framework/react/guide/preloading#preloading-with-external-libraries
    defaultPreloadStaleTime: 0,
    context: {
      // auth will initially be undefined
      // We'll be passing down the auth state from within a React component
      auth: undefined!,
      apolloClient: client,
      preloadQuery: createQueryPreloader(client) as any, <--- Couldn't find the correct type for this
    },
  });

  return routerWithApolloClient(router, client);
}

// Register the router instance for type safety
declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>;
  }
}

app.ts

import { RouterProvider } from '@tanstack/react-router';
import Auth0Provider from './providers/auth0-provider';
import { createRouter } from './router.ts';
import { useAuth0, User } from '@auth0/auth0-react';
import ApolloProvider from './providers/apollo-provider/apollo-provider.tsx';

function InnerApp() {
  const auth = useAuth0();
  if (auth.isLoading) {
    return null;
  }

  return (
    <RouterProvider
      router={createRouter()} <-- using the client-integration-tanstack-start router
      context={{
        // There should always be an ID (sub)
        auth: auth as ReturnType<typeof useAuth0<User & { sub: NonNullable<User['sub']> }>>,
      }}
    />
  );
}

export default function App() {
  return (
    <Auth0Provider
    ...
    >
      <ApolloProvider>
        <InnerApp />
      </ApolloProvider>
    </Auth0Provider>
  );
}

$dealId.ts

...

  loader: ({ params, context }) => {
    return context.preloadQuery(DealQuery, {
      variables: { dealId: params.dealId },
    });
  },

...

  const dealQueryRef = Route.useLoaderData();
  console.log({ dealQueryRef });
  const { data } = useReadQuery(dealQueryRef);

This console.log would give me alternating responses between

image
and
image

The latter giving me the

Error message
Expected a QueryRef object, but got something else instead.

when I tried to use the queryRef in the useReadQuery

tldr; There was no difference for me in outcome when using the @apollo/client-integrations-tanstack-start and just using @apollo/client

Just to reiterate I am using Vite with React and just TanStack Router, not TanStack Start.

Do you want me to also post this on that github issue you linked?

Thanks for sharing that context! Yes adding this to that issue would be super helpful. Appreciate it!

I just pinged Manuel Schiller from the TanStack Router maintainers, and he noted that this could be caused by defaultStructuralSharing - could you try setting this to false?

1 Like

This worked! It worked both with and without the @apollo/client-integrations-tanstack-start package