React Query - A practical example.
Tags: react, javascript, webdev, hooks
Published: 2021-03-23 21:36:37


Hello fellow developers!

Since the appearance of new features in React, such as "Hooks" and "Context,” we began to find Redux substitutes when we managed our app’s state. I made a small example of using React Query for this purpose in this opportunity, and I will talk about some great features it has. 

{% github OtamNitram/movies-react-query %}

To have real data, I used [The movies database API](https://developers.themoviedb.org/3) to fetch data. You need to register yourself and ask for an API token that you can use in my repo. 

## How to use React Query?

### Fetching data. 

If you want to fetch data to be used in the app, it can be done very easily.
React Query provides us the `useQuery` hook to fetch and control de state of the retrieved data. 

``` Javascript
 import { useQuery } from 'react-query'
 
 function App() {
   const { isLoading, isError, data, error } = useQuery('movies', fetchMovies)
 }
```
In the example above, `fetchMovies` is our async call that will return an array with all the movies; this can be an Axios call or a simple fetch. The `useQuery` hook result contains some states we can use in our app. 

* `isLoading` will be true when the query has no data yet, very useful to render a spinner while you can't show data to the user yet. 

* `isError` will be true if the async call returned an error, and, of course, the `error` state will give us more information about it. This is helpful if we need to render some error message when things go wrong. 

* `data` will bring us the result of the async call, and we can use it to render our data and show it to the user. 

If you didn't notice, we never used anything but the `useQuery` hook. We didn't use things such a `useEffect` to pull the data to an `useState` constant. React Query does all of that behind curtains for us. 

If you need more info, check the [documentation](https://react-query.tanstack.com/guides/queries)

### Modifying data in the server. 

Now imagine that you are already fetching and showing the movie data, and you want to add a new movie to the server and, of course, to the list.

To do so, first, you need to declare a new [`queryCLient`](https://react-query.tanstack.com/reference/QueryClient#_top) to have access to the cache. If you want to provide the same client through all the app, you maybe want to use [`QueryClientProvider`](https://react-query.tanstack.com/reference/QueryClientProvider#_top).

Once you have a new `queryClient,` we can use the `useMutation` hook to create a new movie. 

``` javascript
function MoviesList() {
  // Access the client
  const queryClient = useQueryClient();

  // Queries
  const query = useQuery('movies', getMovies);

  // Mutations
  const mutation = useMutation(postMovie, {
    onSuccess: () => {
      queryClient.invalidateQueries('movies');
    },
  });

  return (
    <div>
      <ul>
        {query.data.map((movie) => (
          <li key={movie.id}> {movie.title} </li>
        ))}
      </ul>
      <button
        onClick={() => {
          mutation.mutate({
            id: Date.now(),
            title: 'Terminator',
          });
        }}
      >
        Add Movie
      </button>
    </div>
  );
}
```

There are some important things in the code above. 
We declared our mutation very similar as we declare our queries. To use it, we call the `mutate` function and pass the payload as parameters. Once the async call is done, we can use the `onSuccess` option and invalidate the query we were using and refetch the data again by calling `invalidateQueries`. 

Similar to the `useQuery` hook, the `useMutation` hook also came with useful states in its response. 

``` javascript
const { isLoading, isError, error, data, isSuccess } = useMutation(postMovie, {
  onSuccess: () => {
    queryClient.invalidateQueries('movies');
  },
});
```
We can use `isLoading` to indicate that something is being posted to the server. `data` will give us the response of the async call. `isError` and `error` will give us information in case something wrong happens. 

For more information, check the [documentation](https://react-query.tanstack.com/guides/mutations)

## Pagination and "Load More"

In my example in the [github repo](https://github.com/OtamNitram/movies-react-query) I wanted to fetch all the movies, but the API forced me to fetch page by page. Thankfully, React Query also has a solution for this kind of case. 

Very similar to when we use the `useQuery` hook, we can use the `useInfiniteQuery`, which has more useful state properties and a different and more suitable way to handle the data. 

```javascript
const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery('movies', getPopularMovies, {
    getNextPageParam: (lastPage) =>
      lastPage.page < lastPage.total_pages ? lastPage.page + 1 : undefined,
  });
```

The first difference with `useQuery` is the structure of the retrieved data. `data` contains `data.pages` that is an array of all the pages retrieved where every page contains what you retrieved in the API. `data.pageParams` has all the parameters used to fetch the pages. 

My `getPopularMovies` expect a parameter with the next page's number to be fetched behind the curtains. The first time `useInfiniteQuery` runs it will fetch `page = 1` then `getNextPageParam` will calculate the next page if any. 

We can use `hasNextPage` to run again `useInfinitQuery` and fetch the next page, `isFetchingNextPage` indicates that the call is being called, and `status` tell us if everything was ok or an error occurs. 

Maybe it cannot be evident at first, just only seeing a piece of the code, so I invite you to go to my repo and take a better look. 

## Other cool stuff

I want to include in the future some other cool features React Query has. 
[**Prefetching**](https://react-query.tanstack.com/guides/prefetching) allows you to fetch the data before it's needed, ideal for the cases that you can anticipate what the user will need.
[**Optimistic Updates**](https://react-query.tanstack.com/guides/optimistic-updates) will do the obvious, but the cool thing is that you can rollback your update if anything went wrong. 
Also, I recommend looking at how [**caching**](https://react-query.tanstack.com/guides/caching) works.

## Devtools

React Query has a great devtools feature that can be easily being installed. It will show you the current state of your app. 

```javascript
 import { ReactQueryDevtools } from 'react-query/devtools'
 
 function App() {
   return (
     <QueryClientProvider client={queryClient}>
       {/* The rest of your application */}
       <ReactQueryDevtools initialIsOpen={false} />
     </QueryClientProvider>
   )
 }
```

## Final Thoughts

React Query is a powerful tool but doesn't completely replace other global state managers such as Redux or Mobx. There are some cases where React Query can work very well with other client-state libraries.

I hope this gave you an overlook of this awesome tool. Feel free to leave a comment with suggestions, questions, or if you think some changes have to be done in the repo. 

{% github OtamNitram/movies-react-query %}

Thank you for reading!