React Router vs React Query: There Will Be Blood!

Fermin Blanco
Stackademic
Published in
4 min readNov 27, 2023

--

React Router and React Query as wild animals

Wait a minute! are they antagonists? TLDR: They are not But I like the violent title.

Abstract

I stop by an article from Kent C. Dodds when he was comparing the capabilities from React Router and React Query. Besides, he was making them both to play together. I did find that approach very amusing to try, so here we will explore and combine the data fetching capabilities from React Router while keeping the data caching skills from React Query.

STOP THIS MADNESS AND SHOW ME THE CODE

React Router has Data Fetching APIs

React router V6 besides its new data routers is bringing data fetching concepts to client renderer applications. Because, as expressed by Ryan Florence,

the sooner you fetch the better the user experience.

Well, fetching as early as possible could improve not only the user experience but the developer experience. Since we no longer have to deal with pending and fallback states but spinner components in our UIs. Although this is not a novelty idea, since Server Side Rendering depict this future: having the data available before rendering the component.

Traditional UIs such the ones using React Query follows the below pattern:

React Query Data Fetching

As shown by the code we are bound to pending and error states on every page where server data is needed. On top of that, the code is no longer ease to follow and the complexity of managing additional states makes our code to smell.

Data Fetching with React Query

After a long blank page we get the fallback UI and then the data.

The React Router Proposal: fetch as early as possible.

React Router Rendering pattern

By the time the component mounts, the data is already there.

Aha! no pending states, no fallback UIs, no error states 😍. And the code for the loader.

React Router Loader definition

Since page transition happens when the data is available, the first route the user hits (when navigating to our application site) will still render a long blank page before anything 🥲.

But subsequent page navigations will state in the current route before data for the next route arrives. That is why, we no longer care about error states or fallback UIs inside React Components.

One downside of this approach is that in Development with React Strict mode enable, React will call the loader and therefore fetchTodos function twice 😡.

React Router is not a cache

But when navigating between pages (back and forth), the mayor downside is not able to render stale data, then re-fetch and fianlly re-render if any updates exits.

React Router Loader for fetching todos

Although, this is possible with React Query due to its cache capabilities. In other words, when we visit a route that is using React Router Loaders, that data is being re-fetch in every visit.

Is it not possible to have the best of both worlds (fetching early and having a cache)?

React Router meets React Query

React Router Loader now is using a query client to cache the results from fetchTodos

And inside the component:

React Router Query rendering pattern

Many approaches use the hookfrom the React Query library but I prefer this one since it did not cause any additional re-render for the first load. It will render the component the first time with no data and then re-render with the data available.

The problem with useQuery from the React Query library

Maybe some configuration in my implementation using the useQuery hook was wrong (please 🙏 shed some light in the comments). But the first time the component renders the data came undefined.

Use query data gets undefined the first render when using with React Router loaders

Data invalidation

One interesting challenge still remained, how to invalidate queries? since they are being cached by ensureQueryData

Using ensureQueryData from query client to retrieve data from cache if exist or fetching otherwise

🤔 query invalidation using invalidateQueries API would not work, so I was in panic 😱.

Thankfully, React Query comes with pretty handy APIs for clean out the cache.

Removing queries from cache

Conclusion

The real value from integrating data libraries (React Query and React Router) is that we can still use data cache skills from React Query and keep the fetching-sooner feature that React Router loaders is bringing to us.

Common issues

When approaching to this data libraries integration the common pattern is:

Use query with React Router loader

But I was getting undefined from my query, in the first render. I could use isPending return value from useQuery API and then come back to a fallback UI (but that was out of question).

useQuery with React Router loader and having a fallback UI

That will blurry away one of the benefits from using data loaders from React Router.

Resources

Stackademic

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏
  • Follow us on Twitter(X), LinkedIn, and YouTube.
  • Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.

--

--