FrankWiles.com

Ergonomic React Queries

This post is mostly for future me as I’ve done this on a few projects, but for some reason keep reverting back to a suboptimal pattern.

I use React and react-query a LOT. It’s the only way to fly when it comes to keeping state in a React app. The defaults and ability to easily cache and invalidate responses saves me a TON of time in every projects while still providing a great user experience.

However, I often make it a little harder on myself than it needs to be. I blame some mild PTSD from my early days getting used to React hooks.

Filesystem Layout

To give you some more context, most of my projects have a file system layout like this.

shell
src/
    queries/
        users.js
        folders.js
    components/
        ...
    views/
        folders/
            FolderList.jsx

So 99% of the time my queries are being used from either components or views. Mostly views.

The Bad Pattern

You can read up more on how react-query works, but here is an example of the “bad pattern”. Ok it’s no bad, it’s just not great. This is my first inclination, but it leads to lots of subtle bugs around mistakes in your query keys and query options.

I’m sad to report I’ve spent many hours hunting bugs that ended up being the difference between ["users", 14] and ["user", 14]. Not to mention finding these from teammates.

// In FolderList.jsx
import React from 'react'
import { useQuery } from "@tanstack/react-query"
import { Link } from "react-router-dom"

import { getFolders } from "../../queries/folders"

export const FolderList = () => {
  const folders = useQuery({
    queryKey: ["folders"],
    queryFn: getVendors,
    staleTime: 60 * 1000 * 2,
    cacheTime: 60 * 1000 * 5,
  })
  // ...
}

Here we’re direclty importing useQuery(), our fetching function, and settings options. And we have to repeat this all over the place, anywhere we’re grabbing our list of folders from the API.

The Good Pattern

I’m not sure why I have this ingrained feeling that hooks are different. The better pattern is to define all of this in queries/folders.js and just return it.

javascript

// In queries/folders.jsx
import { useQuery } from "@tanstack/react-query"

export const useFolders = () => {
  return useQuery({
    queryKey: ["folders"],
    queryFn: getVendors,
    staleTime: 60 * 1000 * 2,
    cacheTime: 60 * 1000 * 5,
  })
}

// In views/folders/folderList.jsx
import { useQuery } from "@tanstack/react-query"
import { useFolders } from "../../queries/folders"

export const FolderList = () => {
  const folders = useFolders()
  // ...
}

Isn’t that so much nicer?

Headshot of Frank Wiles

Frank Wiles

Founder of REVSYS and former President of the Django Software Foundation . Expert in building, scaling and maintaining complex web applications. Want to reach out? Contact me here or use the social links below.