Skip to content
Swizec Teller - a geek with a hatswizec.com

How React Query gives you almost everything you thought you needed GraphQL for

Friend, how often do you write a piece of logic like this?

function Component() {
const [data, setData] = useState(null)
async function fetchData() {
const res = await fetch("https://example.com/api")
const json = await res.json()
setData(json)
}
useEffect(() => {
fetchData()
}, [])
if (!data) {
return <p>Loading ...</p>
} else {
return <DoStuffWithData data={data} />
}
}

You've got state for your API result, an async fetching function, an effect that runs on component mount and calls fetchData. Rendering shows either a loading state or your API result.

Works great, solves the problem, done.

Fiddly and wasteful

You can write this in your sleep after 3 months on the job. Super common piece of boilerplate.

And it's easy to make it smart! You can re-fetch data when a prop changes.

function Component({ itemId }) {
const [data, setData] = useState(null)
async function fetchData(itemId) {
const res = await fetch(`https://example.com/api/${itemId}`)
const json = await res.json()
setData(json)
}
useEffect(() => {
setData(null)
fetchData(itemId)
}, [itemId])
if (!data) {
return <p>Loading ...</p>
} else {
return <DoStuffWithData data={data} />
}
}

Your effect re-runs when itemId changes. Data re-fetches, component re-renders, UI looks fresh. 🧙‍♂️

Pretty neat huh?

Except boilerplate sucks my friend. Nobody likes writing boilerplate.

And this stupid thing is always juuuuust custom enough that you can't extract into a custom hook. Every component and every API request are different. Not a lot. Just enough.

Oh and what happens when you render this component twice?

<Component />
<Component />

You spam users and servers with API request. Fetching the same data twice. Wasteful.

Looks contrived in this example, happens a lot in big projects. A component that fetches its own data, like it should, sneaks into 3 different parts of your UI.

Boom – 3 parallel requests for the same result. 🤦‍♀️

Time for global state?

The common reaction at this point is to add global state management.

Add a library like Redux or MobX, maybe XState, hoist data fetching into a new layer of your app, build machinery to manage state in a central place and ...

It's a lot of work. And a mistake.

Codebase gets 3 orders of magnitude more complicated. Anything you do from now on has to happen in 3 places, touch a bunch of code, and everything depends on everything else. Your system needs constant tuning. Nothing is isolated.

Bleh.

You can try GraphQL

As I've mentioned before, GraphQL blows REST out of the water. Solves every problem I've ever had.

Large unspecific payloads? Solved.

Merging many requests into 1? Solved.

Deduping API requests? Solved.

Reusing API requests across components? Solved.

Caching results and re-fetching when stale? Solved.

You need Apollo Client for the last 3 benefits and hey that's what almost everyone uses. 🤘

Our example looks like this with GraphQL:

const QUERY = gql`
query GetData($itemId: String!) {
data(itemId: $itemId) {
...
}
}
`
function Component({ itemId }) {
const { loading, data } = useQuery(QUERY, {
variables: { itemId },
})
if (loading) {
return <p>Loading ...</p>
} else {
return <DoStuffWithData data={data} />
}
}

You write a query for your data, pass it into the useQuery hook, and Apollo handles the rest. Data is cached, re-fetched when needed, requests are deduped, and your component is simpler.

Rendering twice

<Component />
<Component />

makes a single API request.

You can even make a custom hook and reuse in many places.

function graphqlMyData(itemId) {
return useQuery(QUERY, {
variables: { itemId },
})
}

Use that anywhere in your code and if you use the same itemId, your requests are deduped. Use different ids and I think Apollo is smart enough to merge your requests.

Good luck using GraphQL with an existing backend

Your existing backend uses REST doesn't it my friend?

Yeah, me too. Suggest we could solve CurrentProblem with GraphQL and people brush me off as a joke.

Oh Swiz you so funny, of course we can't rewrite our entire ecosystem to use GraphQL. Cute, keep joking you jokester 😂

You've got a system that works. A system that's more than just an API reading the database and spitting out JSON.

Bet you there's heaps of business logic in there. Data validation, wrangling, reshaping, confirming everything works, keeping different systems talking to each other, and ensuring you have less work to do on the frontend.

That's why systems like Hasura are a bad idea if you ask me. You don't want to expose your entire database model to the frontend raw. Ever.

You need a well-designed API that hides underlying complexity.

That makes GraphQL fantastic for new projects and terrible for existing systems. It's too much work to switch.

React Query to the rescue

React Query solves almost all those problems without rewriting your backend.

Keep using REST, get almost all the benefits of GraphQL.

Here's how it works:

  1. Write your data fetching functions
  2. Wrap them in a named query
  3. React Query handles caching, deduping, re-fetching, and loading states
async function fetchData(itemId) {
const res = await fetch(`https://example.com/api/${itemId}`)
const json = await res.json()
return json
}
function Component({ itemId }) {
const { isLoading, data } = useQuery(["fetchData", itemId], fetchData)
if (isLoading) {
return <p>Loading ...</p>
} else {
return <DoStuffWithData data={data} />
}
}

Back to the example we had before.

fetchData is a data fetching function, gets itemId as an argument, and returns JSON. This is important: React Query expects async functions that return JSON.

You could use anything to generate that JSON. REST, GraphQL, Websocket, complicated javascript computation. Anything. React Query doesn't care ✌️

Pass that into useQuery with a key. You can use string keys, but I like the array version by default.

An array key works like the dependency array of useEffect. First argument is always a string, this names your query, the rest are dynamic props.

React Query passes those props into your fetcher function as arguments and ensures your query re-runs when they change.

I like to go even further and wrap queries into custom hooks:

async function fetchData(itemId) {
const res = await fetch(`https://example.com/api/${itemId}`)
const json = await res.json()
return json
}
function useItemData(itemId) {
return useQuery(["fetchData", itemId], fetchData)
}

Now you can put useItemData(itemId) anywhere in your codebase and React Query handles the rest.

No worrying about global state, no dealing with caching, no fuss around loading states, no thinking about re-fetching, no worries around stale data, no rewriting your backend.

What React Query can't do

React Query doesn't understand your queries like GraphQL libraries do. Means it can't merge multiple requests into 1 for you, can't reduce the size of your payloads, and can't help you validate the server returned what you were expecting.

And for most usecases that's perfectly fine.

Cheers,
~Swizec

PS: you can start using React Query alongside your existing code ✌️

Did you enjoy this article?

Published on September 7th, 2020 in

Learned something new?
Want to become a high value JavaScript expert?

Here's how it works 👇

Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

Start with an interactive cheatsheet 📖

Then get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career.

"Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"

~ Ashish Kumar

Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

Ready to Stop copy pasting D3 examples and create data visualizations of your own?  Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️