React Query
Overview
Once you start learning React and how it works, you won't see any problems getting data. Components play an important role in React applications as they are responsible for rendering content. The component is not responsible for retrieving data from the API. There are many ways to retrieve data in React applications. You can use APIs and libraries in React applications, such as Fetch API, Axios library, and custom React hooks you can create yourself.
Introduction to React Query
React Query (RQ) is a high-performance and powerful data synchronization library for React apps. Provides a collection of hooks for retrieving and managing data. It's backend agnostic, so you can use REST, GraphQL, or any other API and don't care about RQ. React Query handles caching, background updates, and stale data on the fly with no configuration. RQ's caching layer is powerful and requires minimal configuration.
React Query simplifies state management by making it almost trivial to retrieve, modify, and cache data. It can also be customized for advanced use cases. React Query does a lot, but RQ is not a complete replacement for client state management libraries as it cannot handle UI state (the state used to control the interactive parts of the app). A library for retrieving and synchronizing data.
However, RQ is designed to replace the boilerplate code and associated writing used to manage cached data in the client state with just a few lines of code. RQ should manage asynchronous operations between server and client, and handle UI state using Redux, Mobx, State, or React Context. This simplifies your app's logic and gives your user a faster experience with less code.
Installation
Let's first set up a simple React.js application.
Create a pre-build React.js application in the react-query-demo directory with the following command.
You can install React Query with NPM or Yarn
NPM
Yarn
To configure React-Query in your React.js application, you need to wrap the component that needs to retrieve data with a QueryClientProvider component.
Child components of QueryClientProvider now have access to hooks provided by the React-Query that provide QueryClient instances. Use this instance to access hooks provided by the React-Query library.
To get started with React-Query, use a basic configuration in your root index.js directory file like this:
React Query List of Features
Apart from the advantages already mentioned, React Query is one of the reasons why you should use this library in your next project.
- Caching: Regain Window Focus - This allows you to prefetch data in response to application activity.
- Request retry: Ability to set the number of retries for a request in case of an error.
- Prefetching: Data can be prefetched depending on whether the application needs new data after an update request. Data can be updated in the background.
- Optimistic updates: It can even handle complex caching, so your application is fully optimized.
- Memoizing query results
- Deduplicates multiple requests for the same data into a single request.
- Mutations that facilitate the implementation of optimistic changes.
Prefetching
Let's say you have an e-commerce app and you have a preview feature that directly shows minimal information about a product when you hover over it. In that case, prefetching the data is recommended for better UX.
Optimistic Updates
Updating the state optimistically before executing the mutation can cause the mutation to fail. In most of these error cases, the optimistic query can simply trigger a refetch to get back to the actual server state. However, under certain circumstances, fetching may not work properly, and mutation errors may indicate server problems that are preventing fetching.
In this case, you can undo the update instead. Updating the status optimistically before running the mutation can cause the mutation to fail. In most of these error cases, the optimistic query can simply trigger a refetch to get back to the actual server state. However, under certain circumstances, fetching may not work properly, and mutation errors may indicate server problems that are preventing fetching. In this case, you can undo the update instead.
To do this, use the "onMutate" handler option of the "useMutation" to return a value that is later passed as the last argument to both the "onError" and "onSettled" handlers. In most cases, passing a rollback function makes the most sense.
Updating a list of todos when adding a new todo
Updating a single todo
You can also use the "onSettled" function instead of separate "onError" and "onSuccess" handlers if you prefer.
Does React-Query Qualify as a Request Framework?
React-Query does more than just manage the query state cycle. It provides out-of-the-box tools for your network needs, including: B. Refetching, Prefetching, Caching. Share query status across the React tree accessible via a key. It has an out-of-the-box mechanism to keep data up-to-date by automatically refetching data when needed and defining it as stale. It also provides tools for managing mutation requests (accessed via the useMutation hook).
But above all, React-Query comes with a new and different paradigm. According to this paradigm, server state has never been properly managed. UI state and server state are different in nature and have different requirements and should be trated separately.
Making a POST Request using Reputation Hook
React Query requires a funtion that returns a promise. In this section of the article, we'll look at how to create a post request i.e. sending data from the application to the backend. Let's see how easy it is with React Query.
Form.js
App.js
Explanation of the above code
- First of all, we had to create a component called Form.js.
- Use the useMutation() hook that returns isLoading, Error, and Mutate functions.
- These functions are used to build requests and include values in requests. It also has an async function as an argument. isLoading and error handle mutation functions seen during the API call process.
- create an asynchronous function called createName that uses the Axios library to send an HTTP POST request to the API (https://jsonplaceholder.typicode.com/users).
- Import the useState() hook to get the state of the input element.
Now you can see on your localhost:3000
Example Application
If you want to get data to use in your app, it's pretty straightforward. React Query provides a useQuery for retrieving and controlling the status of retrieved data.
In the example above, fetchMovies is an asynchronous call and return an array containing all the movies. This is either an Axios call or a simple fetch. The result of the useQuery hook contains some states that your app can use.
- isLoading is true if the query does not contain any data yet. This is very useful for rendering spinners when no data can be displayed to the user yet.
- isError is true if the asynchronous call returned an error. Of course, the error status will give you more information about it. This is useful when you need to print an error message when something goes wrong.
- data provides the result of the asynchronous call, which can be used to render the data and display it to the user.
In case you haven't noticed, I didn't use anything other than the useQuery hook. I didn't use anything like useEffect to pull data into the useState constant. React Query does all of this behind the scenes for you.
Modifying Data in the Server
Now let's say you've already retrieved and displayed the movie data and want to add new movies to the server and list.
For doing this, we need to create a new queryClient that can access the cache. When using QueryClientProvider to serve the same client consistently.
After that, we used the useMutation hook for creating the new movie.
When declaring the query, I declared the mutations to be very similar. To use it, call the mutate function and pass the payload as a parameter. After the asynchronous call completes, you can use the onSuccess option to invalidate the queries you used and call invalidateQuaries to retrieve the data again.
Like the useQuery hook, the useMutation hook's response contained a useful state.
- isLoading is used for indicating that something is posted to the server.
- Data gives us the response to the asynchronous call.
- If something happens wrong, then isError and error will give us information about it.
Pagination and "Load More"
I wanted to get all the movies, but the API required me to get them page by page. Fortunately, React Query has a solution for this type of case.
Similar to using the useQuery hook, you can use useInfiniteQuery which has a more convenient state property and a different, better way of handling the data.
The first difference with useQuery is the structure of the data retrieved. data contains data. pages which is an array of all fetched pages. Each page contains what you fetched with the API. data.pageParams contains all the parameters used to fetch the page.
My getPopularMovies expects a parameter with the number of the next page which is retrieved behind the scenes. The first time useInfiniteQuery runs, it gets page = 1 and getNextPageParam calculates the next page, if any.
You can use hasNextPage and run useInfiniteQuery again to get the next page. isFetchingNextPage indicates that the call was called and the status indicates whether everything was fine or an error occurred.
React-Query — The pitfalls
Some good cases using React-Query
- Fetching and caching a single resouce by ID
- Get the list of options and save it for the rest of the user journey.
- Get a finite list of items using some filters
When things get worse
Here are some cases where it's useful to move data retrieval away from the component layer.
- When you need fine-grained control over the order of network requests.
- When the response to one network request is required for the next network request.
Let's say you have a dashboard with many components that start fetching as soon as the page loads. What if you want to make fewer calls at the same time? Using dependent queries can help, but you have to put more business logic into your hooks.
Let's say we have another list of 50 items. One day, the product owner asked me to add more metadata to each listing of her item. This metadata is unrelated to the original call and is probably obtained from a third-party service. By moving the fetching responsibility to the component level, we're looking for a good 50 concurrent calls. Just because you've bundled your business logic into it, you have to completely change your component (view) structure to make it work together. This comes at the expense of flexibility.
Again, React Query is fine. Not the ideal tool for the fine-grained control you need. Something like RxJS is more similar.
Other code smells
Here's an interesting example of paginated results using a cursor. If you're not familiar with cursor-based pagination, you can't go to page2. You have to request the first batch of data first, and the backend tells you about the cursor string that unlocks the next page.
So when you make a request, the API responds like this:
Then trigger the onSuccess callback to notify the store of the latest cursors and cache the data.
Here's the catch. The onSuccess handler will not be triggered again until the data is stale.
Example
If you change the category and overwrite the Cursor object, it will be served from the cache when you change it back. The onSuccess handler is not triggered and still sends undefined to the backend and responds with the same first batch of data.
The solution is to use useEffect and manually check the payload each time, but again you shouldn't do that.
Journeys need to be in full control, not just to display results, so fetching data should be done away from the component.
It's trivial, but what if you need more complex orchestration?
React lacks widely accepted standards for building applications. This is great. There are many ways to deal with the problem, but they have trade-offs.
React Query is a great tool. A tool that can save my bacon 100 times and retrieve data without worrying about status or caching itself. However, it is not the only tool that can solve everything.
At some point, you may encounter strange, complex, and unexpected requests. Placing network requests at the component level is not always the best approach. So instead of trying to trick the tool into doing something irresponsible, be vigilant and be prepared to make the appropriate adjustments. I learned this the hard way.
Conclusion
- The React Query library helps manage data related to web service requests, improving user experience while making applications easier to maintain as complexity increases.
- React Query provides a powerful set of React hooks for fetching data in your React application. There are a number of out-of-the-box features that help you worry about what data you retrieve rather than how you retrieve resources on the server.
- React Query is a powerful tool, but it's not a complete replacement for global state managers like Redux or Mobx. React Query may work well with other client-state libraries.
- There are more powerful features that React-Query can do, among which are parallel queries, dependent queries, paginated queries, infinite queries, and more. All of the above-mentioned React Query is most commonly used.
- React Query manages cached data and runs asynchronous processes. Perform background updates. It also replaces all the boilerplate code that Redux and others use to manage their data.
- React Query is often described as a missing piece of the React ecosystem, and this tutorial provided an overview of this great tool.