React Query - A practical example.

otamnitram

Martín Mato

Posted on March 23, 2021

React Query - A practical example.

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.

Welcome to movies-react-query 👋

Version Twitter: otamnitram

An example of react app using react-query and tailwind CSS

Prerequisites

  • node >=14.15.4 <15.0
  • npm >=7.0.0

Install

npm install
Enter fullscreen mode Exit fullscreen mode

Usage

npm run start
Enter fullscreen mode Exit fullscreen mode

Run tests

npm run test
Enter fullscreen mode Exit fullscreen mode

Author

👤 Martin Mato

Show your support

Give a ⭐️ if this project helped you!






To have real data, I used The movies database API 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)
 }


Enter fullscreen mode Exit fullscreen mode

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

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 to have access to the cache. If you want to provide the same client through all the app, you maybe want to use QueryClientProvider.

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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

Pagination and "Load More"

In my example in the github repo 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.



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


Enter fullscreen mode Exit fullscreen mode

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



 import { ReactQueryDevtools } from 'react-query/devtools'

 function App() {
   return (
     <QueryClientProvider client={queryClient}>
       {/* The rest of your application */}
       <ReactQueryDevtools initialIsOpen={false} />
     </QueryClientProvider>
   )
 }


Enter fullscreen mode Exit fullscreen mode

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.

Welcome to movies-react-query 👋

Version Twitter: otamnitram

An example of react app using react-query and tailwind CSS

Prerequisites

  • node >=14.15.4 <15.0
  • npm >=7.0.0

Install

npm install
Enter fullscreen mode Exit fullscreen mode

Usage

npm run start
Enter fullscreen mode Exit fullscreen mode

Run tests

npm run test
Enter fullscreen mode Exit fullscreen mode

Author

👤 Martin Mato

Show your support

Give a ⭐️ if this project helped you!






Thank you for reading!

💖 💪 🙅 🚩
otamnitram
Martín Mato

Posted on March 23, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related