state manager for cash lovers

Hello everyone! My name is Andrey Demyanov. I am a team lead and developer at MTS Travel. Together with other teams, we are creating a hotel booking service in Russia and around the world. We are developing from scratch, so we experience the need for new libraries, approaches, and changes related to expanding and improving capabilities.

In this article, I want to talk about our experience working with the React Query library (now TanStack Query, hereinafter RQ) and why we chose it. And also how it helps us simplify and speed up access to pages and data, save resources on similar queries, simplify visualization of working with data and untangle the code. There will be no deep technical details, but the article may be of interest to those who want to learn about the experience of using RQ in product development.

How we chose the library

When laying out the initial architecture, we decided to start from the needs for storing and processing data on the client side, without giving in to temptations and internal voices.

The first tool for us was MobX. It closed the list of basic requirements: storing, processing and reusing the results of backend responses. But MobX did not meet the requirements that we had for it in the future – for example, we began to lack built-in caching for typical requests and the ability to control the entire cycle of working with data for the client.

We lived with MobX for several months, but the results achieved were not enough to feel satisfied and stop searching. We started storing some data on the client, but this did not provide a significant increase in productivity and speed.

The number of similar search queries increased, product managers continued to come up with new features to expand the functionality of the pages, and the product decided on its development course. Now the user had to be able to quickly move between the search results and the page with information about the hotel. At the same time, it was desirable that this happened as quickly as possible and no one got bored in the process.

After these events, the demands with which we enter the library market became clear:

  • We couldn't set up a convenient transition from the details page back to the search page.

  • What if we don't want to do props drilling, but need to be able to get data at different levels of nesting? And at the same time not to get overgrown with monotonous code? Let me explain that props drilling is the process of passing properties (props) through many levels of nested components in React, even if intermediate components do not use these properties directly. This can lead to code bloat, a decrease in its readability and manageability, and to the occurrence of unnecessary redraws. All this negatively affects the performance of the application.

  • How can we get loading states without hurting the user? So that it is convenient and effective?

We formulated the requirements and realized that the best option was React Query.

What is React Query

React Query (or TanStack Query) is a library for working with asynchronous operations, updating them in the background, and synchronizing states. It frees the team from the need to write repetitive code and manage the state manually. In essence, React Query is a set of hooks and utilities for working with asynchronous requests, caching, and synchronizing data with the server.

One of the main advantages of RQ is its ease of integration and the ability to select only those functions that are needed for a specific project. The library simplifies loading, updating and caching data, making these processes more efficient and predictable. It also makes it easier to work with errors and implement an optimistic UI. This means that applications become more responsive and user-friendly.

How the library compared with its competitors can be seen at official websiteThere is also a comparison of bundle sizes, speed, support for query syntaxes, and even the number of stars on GitHub.

Our problems and attempts to solve them

The main problem for us was the need to cache requests with unpredictable latency. Long delays when switching between pages led to users leaving the service or having a negative experience using it.

To solve the problem, we used several RQ mechanics:

  • Caching by unique key. That is, the ability to use a string value as an identifier. This helps tie the need to request new data to a check to see if a request has already been made with similar search parameters.

  • Repeat requests after a certain time. This RQ property allows us to automate the updating of data on the page without writing extra code. In this case, we only need to explicitly pass on how long it will take to make a new request.

  • Transferring download progress statuses. This is a collection of flags that allow you to fine-tune the visualization of the loading progress for the user.

We initialized the project root and created a root provider. It consisted of two mandatory imports – QueryClient and QueryClientProvider. The first of them is an object instance. It is used at the moment of initialization of the queryClient variable. The second is used as a provider that receives our queryClient in the client field. All this will help in the future to receive data stored in RQ for all places that were wrapped by the imported QueryClientProvider.

At the same time, we imported the custom hook we created for caching RQ in IndexedDB, which is called after the page is rendered (you can read about the persistQueryClint method in documentation):

Next, we needed to create a new useQuery hook that would accumulate our work with data and allow us to access query results only where necessary.

The first thing we need here is a function that will act as a key generator. The key combines search results parameters that we consider unique – for example, date, number of guests, selected filters, and so on.

A key in RQ is an array of two elements. The first is a name for which we then store values. The second is a unique string that lets us know whether we need to retrieve the result from the cache or make a repeat request.

Once the key is created, we are ready to initialize the custom hook that will help us handle all of our needs.

The response object is a collection of many different statuses about the request:

  • classic isLoading (the loading process) and isSuccess (data is ready for use);

  • isLoadingError — handling of loading error;

  • isFetching — visualization only response time;

  • isError — error;

  • dataUpdatedAt – find out the time of data update and so on.

Briefly about deduplication

After working with the request, all we have to do is use the newly-minted useHotelOffers hook (from the screenshot below). With its help, we get data about hotels, pass the necessary properties to it, and get the desired responses about the state and data:

There are two points to note here:

  1. To repeat the data request, we need to update the properties that we pass to the hook. Any change to them will result in getting into the RQ cycle – this is when we check for a cached response and return the previously calculated result or make a new request to the server. Therefore, pay close attention to what should be the trigger for changes.

  2. We receive data only in a specific place, which means we don't need to drag the query result through the thickness of components (by calling props drilling). We can call our hook pointwise, passing only a couple of necessary props to it. This will allow us to avoid unnecessary re-renders, make the code simpler and more readable, and add predictability to the responses.

The final

The simplicity of managing server request states, flexible request caching, and a modest amount of code gave us several opportunities:

  • quickly and efficiently display similar pages with a large amount of data. For example, if a user constantly goes to a hotel and returns to the results;

  • update data on timeout, show required errors and visualize data loading;

  • easy access to fine-tuning settings.

And we don’t worry that the amount of code increases monstrously with each wish.

After a year of using the library, we are still not sure that we know all its secrets. But we can definitely say that RQ helped us close all the emerging needs at the stage of interaction with similar requests.

Thank you for your attention!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *