How to load data with react router dom v6 and handling errorpage?

Hi all,

react-router-dom v6 has now a loader property and an “ErrorPage” property. The tutorial is here:

Root.jsx

import { Outlet, Link } from "react-router-dom";
import { getContacts } from "../contacts";

export async function loader() {
  const contacts = await getContacts();
  return { contacts };
}

Main.jsx

import Root, { loader as rootLoader } from "./routes/root";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    loader: rootLoader,
  },
]);

It looks like it has an own query hook, if the query gets error, then it will automatically use the errorElement .

So my question is should we use the apollographql useQuery() or convert it to loader function from react-router-dom???

1 Like

The solution that a member of my team found was this

import { client } from '@/apollo';
import { GET_INDICE_BY_EBOOK } from '@/graphql/queries/indice.queries';

export const loaderIndice = async ({ params, request }) => {
	const { indiceId } = params;

	const { data } = await client.query({
		query: GET_INDICE_BY_EBOOK,
		variables: {
			getIndiceEbookId: indiceId,
		},
	});

	return data.getIndiceEbook;
};

The routes looks like this:

{
    path: PRIVATE_ROUTES.INDICE,
    element: <Indice />,
    loader: loaderIndice,
    errorElement: <Page404 />,
},

Still is not the best way because ur importing all the client just to make the async request, we still looking a better way to do it with hooks.

Hope this hepls u

2 Likes

Hey @Conny_Gu :wave: !

This is a great question, and to be frank, we (the Apollo Client team) are still figuring out a good recommendation ourselves. Below are my personal thoughts on the subject and the Apollo Client team may ultimately have a different recommendation in the future.


Those new APIs are super neat and solve a ton of problems that have plagued react apps in the past (request waterfalls for nested routes, for example). If you want to take advantage of these new APIs, you’ll have to abandon the hooks approach since loader is just a function that gets executed by React Router when a route is about to transition. Hooks just don’t work here. That means you’ll have to use client.query directly to fetch results in the loader function. This has the advantage that you don’t have to wait for the component to mount before fetching that data, and React Router can optimize loading several requests in parallel for nested routes (something that was VERY difficult in versions prior to v6).

The disadvantage here is that since you’re no longer using useQuery, you won’t receive automatic cache updates if new data is written to the cache for any data used by your query. In this case, you might consider setting up a useQuery hook in your route component that uses a cache-only fetch policy. This avoids the network request, but allows your component to receive cache updates from that point forward.

This might look something like this (caution NOT TESTED):

// Root.tsx
import { gql } from '@apollo/client';
import { Outlet, Link } from "react-router-dom";

const QUERY = gql`
  query { ... }
`;

export async function loader() {
  const { data } = await client.query(QUERY);

  // Return value is thrown away since we are now relying on the cache for the data
  return {};
}

export default function RootRoute() {
  const { data } = useQuery(QUERY, { fetchPolicy: 'cache-only' })

  return <div>{...}</div>
};

This definitely adds a touch of complexity, but gets you the “best of both worlds” (assuming cache updates are important to you).

Ultimately its up to you to decide what kind of UX you want to provide in your app. There are a ton of UX advantages to using the new React Router APIs, but at the cost of nicely using Apollo’s hooks. There is nothing saying you have to use the new loader APIs so you might determine you want to load data the “old fashioned way” in the component like you always have, but just be aware this could introduce request waterfalls for nested routes.

I’ll reiterate, the Apollo Client team may ultimately decide on a different recommendation. We may also look at finding ways to build a tighter integration between frameworks like Remix, Next.js and React Router which might alleviate any of the awkwardness of using Apollo Client with these new paradigms without the use of hooks. Definitely something we will be exploring!

6 Likes

Hi @AngelRmrz @jerelmiller Thanks for the great answers! I think, I prefer to use Apollo’s hooks, they are powerful and the documentation and the Typescript are all clear.

The react-route-dom surprisingly doesn’t have typescript documentation, you have to install the third party type, So everything, you need to try and guess… I just wondering, why they don’t adapt typescript.

1 Like

@Conny_Gu that’s surprising. I’ve used the latest versions of React Router and have had success with TypeScript. In fact, it looks like its written using TypeScript. Maybe its just a documentation issue?

Seems like some of the pages are hit or miss. For example, the useNavigationType doc has an expandable “Type declaration” which shows you the TypeScript types:

Unfortunately it looks like not all pages have the type declaration listed.

1 Like

Hi,

I am testing this approach. You suggest useing client.query:

const { data } = await client.query(QUERY);

but i cannot get acces the ApolloClient . For example

const client = useApolloClient()

does not work since the loader is outside a react Component.

Can you elaborate a bit more to how you would do this?

solved it with:

import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';
class ApolloClientProvider {
    constructor() {
        this.client = new ApolloClient({
            uri: '/graphql',
            cache: new InMemoryCache(),
        });
    }
}
export default new ApolloClientProvider()

and using ApolloClientProvider.client anywhere in the app

1 Like

Hi, adding my 2 cents to the discussion as I’m currently facing this question.

My initial hunch was close to yours @jerelmiller but as others said, the client issue still needs to be solved for every use case. I’d really like to keep digging in that direction as it makes the rendering component work in any application, whether it uses react-router or not. This portability makes me all fuzzy inside :face_holding_back_tears:

@gk123 solution is fine in a lot of cases but I think it can present issues for SSR as the client will be the same for every request.
Issues with cache retention can happen, and worse of all, if two clients can get different results for the same call (i.e. a client in a specific country doesn’t have access to a specific resource) we get a bug here.

I feel a solution could be to create the Apollo client inside the createBrowserRouter, making it unique for each request, but I need to experiment around this.

I’ll try to come up with a test implementation in Vite react with SSR if it can help with the discussion.

Also @jerelmiller, does the Apollo team have a public space with an ongoing discussion about this specific question ?

@JimminiKin unfortunately we don’t really have a single place where this discussion is happening. We’ve had a couple issues related to Next 13 and such, but most of the discussions have been ad-hoc to this point.

I’ll be curious to see where your experimentation leads!

Better avoid the class and creating the instance. This is really just a static singleton variable. Much simpler:

const provider = {
    client: new ApolloClient({
        uri: '/graphql',
        cache: new InMemoryCache(),
    }),
};
export default provider;

or even better with a named export:

export const client = new ApolloClient({
    uri: '/graphql',
    cache: new InMemoryCache(),
});

This is probably not very different from constructing a client and passing it to <ApolloProvider>.
The pattern also appears to be react-router’s current recommendation.
There are [others who face the same problem of not being able to use hooks] github.com<>/remix-run/react-router/discussions/9856)…

Have a look at [this idea] github.com<>/remix-run/react-router/discussions/9861) being dicussed.

(Sorry for the broken links, I’m a new user who cannot post more than two links)

Looking :eyes: forward to update from the team.

I currently agree that if we are using useQuery, we are already buying into many benefits and the added complexity is not worth the added complexity.

Let Router route. Let useQuery load data.