DEV Community

Cover image for Speeding up React Development with TanStack Query
Oscar
Oscar

Posted on

Speeding up React Development with TanStack Query

I just finished implementing TanStack Query (formerly known as React Query) in a project that I’ve been working on for the past month and a half (more on that soon!), and it’s one of the more elegant libraries that I’ve worked with.

It’s very simple, allows for easy synchronization between your frontend and backend, and also great at reducing server load with front-end caching. Let’s go into a bit more detail.

So what even is TanStack Query? 🤔

TanStack Query (TSQ) is responsible for managing responsibilities like data fetching, caching, synchronization, and managing server state (you can read more about this on the documentation – I’d prefer to stay DRY). It’s very possible to build a React frontend without it, but I’ve found that it’s incredibly helpful, and it abstracts away a lot of the repetitive parts of React.

Allow me to give you an example. Here’s what my code looked like before I integrated TSQ:

function Feed() {
  const [posts, setPosts] = useState();
  const [page, setPage] = useState(1);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    getFeed(1).then((res) => {
    setPosts(res);
    setIsLoading(false);
    });
  }, []);

  function formSubmitHelper(e) {
    e.preventDefault();
    setIsLoading(true);
    getFeed(page).then((res) => {
    setPosts(res);
    setIsLoading(false);
    });
  }

  if (isLoading) {
    return <CircularProgress />;
  }

  if (!posts || posts.length === 0) {
    return <h1>There were no posts to be shown!</h1>;
  }
  return (
    <div id="feed">
    {posts.map((content, index) => (
        <BlogPostThumbnail />
    ))}
    <Paginator />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now, if you haven’t used TSQ before, this probably looks pretty good. There’s nothing wrong with the code so to speak; after all, it functions appropriately and passes the unit and integration tests that I wrote for it (more on that in a future post!).

However, this code isn't very extendable or reusable. If I wanted to check for and render an error message, I would have to add in something along the lines of this:

const [error, setError] = useState(“”)

// …

  function formSubmitHelper(e) {
    e.preventDefault();
    setIsLoading(true);
    getFeed(page).then((res) => {
    if(res.ok) {
        setPosts(res);
        setIsLoading(false);
}
else {
    setError(res.error);
}
    });

// …

if (error) {
<h1>{error}</h1>
}
  }
Enter fullscreen mode Exit fullscreen mode

Granted, this is a bit of a rough draft. The actual implementation may take up more “code space”, or it may take up less. Nevertheless, it’s pretty clear that there’s a lot of boilerplate code that is needed just to render a simple error message. This can cause bloat in React applications (in my case, a SPA), as well as code that is exceedingly hard to maintain, debug, and test.

A practical application 💸

This is where TSQ comes in. Instead of adding another useState() hook, we can use TSQ’s useQuery() hook to instead replace almost every useState() hook. Here’s an example based off of the previous bit of code:

function Feed() {
  const [page, setPage] = useState(1);
  const { data, isLoading } = useQuery({
    queryKey: ["getFeed", page],
    queryFn: () => getFeed(page),
  });

  function formSubmitHelper(e) {
    e.preventDefault();
    setPage(e.target[0].value);
  }

  if (isLoading) {
    return (
    <div id="feed">
        <CircularProgress />
    </div>
    );
  }

  if (!data || data.length === 0) {
    return (
    <div id="feed">
        <h1>There were no posts to be shown!</h1>
        <Paginator />
    </div>
    );
  }
  return (
    <div id="feed">
    {data.map((content, index) => (
        <BlogPostThumbnail />
    ))}
    <Paginator />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Instead of adding another useState() hook for every single “thingy” we want to keep track of, we can simply take what we want from useQuery(). If we need to check for an error, we can simply change const { data, isLoading } … to const { data, isLoading, isError } …. Pretty cool, huh?

Some other advantages 👀

Additionally, TSQ handles the function call all on its own. Notice that in the above code snippet, there’s no more useEffect(). We don’t need it anymore, because the function call is completely handled by TSQ. Additionally, each time any of the state involved in the queryFn changes (in this case, when setPage() is called), the query is automatically called again.

"But what about the caching?", you might ask. I’ll elaborate on that right now. TSQ can potentially reduce server load by implementing frontend caching, and while it may sound confusing, it’s actually pretty simple. Here’s a little diagram:

A diagram of TanStack Query's caching system

As you might be able to tell, the cache is basically just a hashmap, where queryKey is the key. This is super useful for… really anything! Since “caching” doesn’t take any extra leg work to set up, TSQ can give you a little performance boost right out of the box!

Wrapping it up 🎁

TSQ is a wonderful library, but one of the things that makes it so wonderful is that it’s incredibly simple, and consequently, there’s not much to write about.

So on that note, thanks for reading! If you have any questions, feel free to ask. However, the documentation might prove a better resource, as I’m still very new to this library.

P.S: At the time of writing this, I’m working on a whole bunch of unit and integration tests with Vitest and Reacting Testing Library, so if you’d like to see an article about that, please do leave a like or follow me so you can be notified when I publish said article!

Top comments (0)