Logo

dev-resources.site

for different kinds of informations.

Speeding up React Development with TanStack Query

Published at
9/25/2024
Categories
react
frontend
javascript
webdev
Author
Oscar
Categories
4 categories in total
react
open
frontend
open
javascript
open
webdev
open
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>
  );
}

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>
}
  }

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>
  );
}

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!

Featured ones: