In this article, we’ll look at the React Query library, how it compares to its alternatives, what its unique product offering is, and how to get started, quickly.
This post is meant for beginners or those brand-new to React Query. The documentation for React Query is excellent and you can read in-depth use-cases and pattern guidance at the React Query documentation.
What is React Query
React Query is a data management library for React, as the name implies. It handles all the major use-cases one would expect for a data fetching library in today’s app ecosystem such as desktop and mobile (React/React Native) support, configurable caching strategies, and various methods for SSR, SSG, and dynamic rendering.
Worth noting as a special callout, “Query” here has nothing to do with GraphQL. In fact, any function that returns a Promise is considered a query. As long as the library you are using is able to return a Promise, you are able to work with React Query out of the box. There are some utilities for working with Websockets which we won’t get into here, but this is a great write-up on the topic if you really want to roll GraphQL subscription support for React Query.
React Query solves a lot of the problems that React developers experience with an “opinionated-first” approach, meaning less overall boilerplate, but with enough escape-hatches for deep customization.
I emphasize the word React developer because part of what makes React Query so special is the narrow problem set it sets out to solve.
How does React Query compare with Apollo, Urql, et al?
React Query, as the name implies, and as mentioned, is for React. That means it’s able to leverage existing React features under the hood like contexts. It knows which mechanisms developers have to work within React and leans into them, providing a hook-driven approach that is by now second nature to most React developers. Let’s consider this comparison more deeply.
Scoped to React
Whereas tooling like Apollo and Urql are able to do much of what React Query can do as well, often you need to write a lot of boilerplate to adjust them to work with React as the tooling is designed to solve for a very wide use-set. With React itself expanding the problem-set it solves, i.e. server-side components, most developers who work with React don’t often need to reach for additional tooling. Having a robust query library tailor-made (or Tanner-made, if you will) for the framework you work with means that the tooling is independently motivated to stay current with your framework of choice.
Scoped to data fetching
React Query only deals with data fetching. Urql and Apollo both concentrate on GraphQL, React Query focuses on data fetching. It’s lower level in the stack, which allows you to embrace your query tooling of choice, like GraphQL Codegen, for example.
Scoped to Server State
One of the primary ways that React Query differentiates from the other libraries is that it relies heavily on “server-state” as being the global source of truth. All of its paradigms are focused on solving problems around syncing data with a server. Where there are a lot of state management libraries, and a lot of data fetching management libraries, and a lot of libraries attempting to do both, React-Query focuses on solving the sync of data with the server. This again reduces the overall amount of boilerplate needed and is a paradigm that fits really well with Hasura’s own thinking about application development.
React Query Basic Concepts
At the top level, React Query provides a client wrapper that handles global data caching and fetching.
function App() {
return (
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
The library then provides a series of hooks that plug into this global controller for use with any fetching method that returns a promise. Even plain fetch will work!
const query = useQuery('todos', getTodos)
Refetch defaults
As part of the library’s intention to sync data with a server, queries get automatically run on sensible actions like window focus and reconnect. More information about React Query’s smart defaults can be found here. These can obviously be fine-tuned, especially during development where refocus will happen quite a bit more than in production scenarios.
Initial data for hydration
Server-side hydration is becoming a most for a good user experience in many applications and this is a standard functionality with React Query.
Dependent queries and success/error event handlers
One of the more innovative features is the ability to set a boolean configuration value for when a query should run, this is a very helpful mechanism to have for cases where sub-components need to wait for an initial query to run. Additionally, you can handle success and error events directly from the query itself for cascading and eventful behavior.
Familiar data methods
As is becoming a familiar pattern for data-fetching libraries, React Query hooks will return a set of “isIdle”, “isLoading”, “isError”, and “isSuccess”. In addition, you’ll all get an error and data object with the actual contents of those two states. Paired with onSuccess handlers, query invalidation, and more, you have a robust toolset for building data-intensive applications.
Restating server-state first
It’s worth restating here as an important concept, but essentially, almost all use-cases where you’d want a helper for fetching your content directly from the server are present. If you’re looking for a local-state heavy utility library, this is not it. But if you want to persist your state on the server and sync the most efficient way possible with an elegant interface and set of utilities, give React Query a try!
React Query for Hasura
Connecting React Query to Hasura is as simply as making a GraphQL request to the Hasura server. As mentioned before, any library that returns a Promise can be called by React Query and added to it’s smart cache. Let’s look at a simple example.
For this project, we’ll use Create React App, and for styles, we’ll utilize Tailwind. Tailwind means we need to use Craco for running our scripts since react-scripts doesn’t let us override the PostCSS settings. None of these implementation details impact how React Query works and are thus out of the scope of this tutorial.
For the index.js path, we're going to add our Tailwind CSS file index.css
, our QueryClientProvider
and the ReactQueryDevtools
which is a great utility for debugging our queries.
import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
const queryClient = new QueryClient();
ReactDOM.render(
<React.StrictMode>
{}
<QueryClientProvider client={queryClient}>
{}
<ReactQueryDevtools initialIsOpen={false} />
<div className="bg-gray-800 h-screen">
<div className="container max-w-5xl mx-auto">
<App />
</div>
</div>
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
And that's all we need to add. Everything else is either default CRA or some style helpers.
Next, we need to make an actual request for our data. We'll do that in the App.js
file.
import { useQuery } from "react-query";
import ColorChip from "./ColorChip";
import ComplementaryColors from "./ComplementaryColors";
function App() {
const { isSuccess, data } = useQuery("data", () =>
fetch("https://intent-shad-91.hasura.app/v1/graphql", {
method: "POST",
body: JSON.stringify({
query: `
{
color {
color
complementary_colors {
color
}
}
}
`,
}),
})
.then((res) => res.json())
.then((res) => res.data)
);
return (
<div>
{isSuccess && (
<div className="grid grid-cols-4 gap-4">
{data.color.map(({ color, complementary_colors }, key) => {
return (
<div
className="w-full h-64 rounded-sm flex flex-wrap space-between"
key={key}
style={{ backgroundColor: color }}
>
<ColorChip color={color} />
{complementary_colors && (
<ComplementaryColors colors={complementary_colors} />
)}
</div>
);
})}
</div>
)}
</div>
);
}
export default App;
import React from "react";
export default function ColorChip({ color }) {
return (
<div className="flex items-center w-full">
<p className="font-bold text-center w-full">{color}</p>
</div>
);
}
import React from "react";
import ColorChip from "./ColorChip";
export default function ComplementaryColors({ colors }) {
return (
<div className="w-full grid grid-cols-3 h-24 self-end">
{colors.map(({ color }, key) => {
return (
<div
className="h-full items-center flex"
key={key}
style={{
backgroundColor: color,
}}
>
<ColorChip color={color} />
</div>
);
})}
</div>
);
}
That's all we needed to add. The magical part we queried from our server was right:
const { isSuccess, data } = useQuery("data", () =>
fetch("https://intent-shad-91.hasura.app/v1/graphql", {
method: "POST",
body: JSON.stringify({
query: `
{
color {
color
complementary_colors {
color
}
}
}
`,
}),
})
.then((res) => res.json())
.then((res) => res.data)
);
Simply passing in a fetch request with a GraphQL body and we get state management for free. Pretty neat!
If you'd like to have a look at the whole project, you can find this React Query start template here.
Next Steps
Not everyone wants to write GraphQL in raw fetch requests, however. In our next article, we'll look at integrating React Query with GraphQL for even better developer productivity!