DEV Community

Cover image for React-query series Part 3: Data fetching with the useQuery hook.
Emmanuel Chinonye Nnajiofor
Emmanuel Chinonye Nnajiofor

Posted on • Updated on

React-query series Part 3: Data fetching with the useQuery hook.

Cover image by Lawrence Eagles in the article: Whatโ€™s new in React Query 3

Hey everyone ๐Ÿ‘‹

I'm back again. I am always grateful for the encouragement. Thank you for the ๐Ÿ’–, for taking the time to read the lengthy Part Two: QueryClient configuration of this series. To my new followers; Thank you! I'll do my best not to disappoint you.

In part two, we talked about overriding some of the defaults that come with react-query by setting our custom defaults for both queries and mutations. We set our custom defaults on such options like retry , staleTime, cacheTime, refecthOnMount and a couple of others for our queries and retry for mutations.

Table of contents

Intro

In this part, we will be learning how to fetch data from an API using the useQuery hook. I promised to show you how we can override the defaults we set earlier, so we will have a look at that too. Always remember to use the table of contents above to jump to sections relevant to you.

The useQuery hook

We start by installing axios and refactoring a bit.

npm i axios
Enter fullscreen mode Exit fullscreen mode

Our QueryClient goes to a new file ./src/util/queryClient.js

import { QueryClient} from 'react-query';

const queryClientConfig = {
    defaultOptions: {
      queries: {
        retry: 2,
        staleTime: 1000 * 30,// 30 seconds
        cacheTime: 1000 * 30, //30 seconds
        refetchOnMount: "always",
        refetchOnWindowFocus: "always",
        refetchOnReconnect: "always",
        refetchInterval: 1000 * 30, //30 seconds
        refetchIntervalInBackground: false,
        suspense: false,

      },
      mutations: {
        retry: 2,
      },
    },

 export const queryClient = new QueryClient(queryClientConfig);

Enter fullscreen mode Exit fullscreen mode

If you just got here, we explained this snippet here

We clean our App.js thus

import { QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
import { queryClient } from "./util/queryClient";


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

We will also create a queryKeys.js file at ./src/util/queryKeys.js.
This file will host all the query keys for our application.

export const fetchPostsKey = "FETCH_POSTS";
Enter fullscreen mode Exit fullscreen mode

Create a fetchPosts.service.js file at ./src/services/fetchPosts.service.js and create your simple async function to fetch a list of posts.
We will be using the JSONPlaceholder REST API for this demo.

import axios from "axios";

/**
 * @desc fetch a list of posts
 */
export const fetchPosts = async () => {
  const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
  return res?.data;
};
Enter fullscreen mode Exit fullscreen mode

Fetching data

Create a Posts.js component at ./src/components/Posts.js

Remember to import your Posts.js component to your App.js

...

 function App() {
   return  (
            <QueryClientProvider client={queryClient}>
              <Posts/>
              <ReactQueryDevtools initialIsOpen={false} />
            </QueryClientProvider>
        )
 }
Enter fullscreen mode Exit fullscreen mode

Posts.js

import { useQuery } from "react-query";
import { fetchPosts } from "../../services/fetchPosts.service";
import { fetchPostsKey } from "../../util/queryKeys";

const Posts = () => {

  const {  isLoading, isError, isSuccess, refetch, remove, data, error} = useQuery(fetchPostsKey, fetchPosts);


  return (
    <div>
      {isLoading ? (
        <div>Loading...</div>
      ) : isError ? (
        <div>An error while fetching posts</div>
      ) : (
        data?.map((post) => (
          <div key={post?.id}>
            <div>{post?.title}</div>
            <div>{post?.body}</div>
          </div>
        ))
      )}
    </div>
  );
};
export default Posts;
Enter fullscreen mode Exit fullscreen mode

The useQuery hook accepts a query key as its first argument and the query function as its's second argument.
The query key is required, the query function also is required as there was not default query function defined for our queries in QueryClient. Let's take a quick brush at the items in the destructured object useQuery returns to us.

isLoading: It is a boolean value that returns true when the query has no data and is currently fetching and false when not.

isError: Also a boolean value. It return true when the query attempt results in an error.

isSuccess: Returns true if the query has received a response with no errors and is ready to display its data. isSuccess is false when query is not yet resolved or results in an error.

refetch : This is a function that manually refetches the query.

remove : This function is used to manually remove the query from cache.

data : It is the response from the last successful query. data will be undefined if query fails for the first time.

error : It is the error response from your query. It is defined when your query is in an isError state.

The useQuery hook returns more values in the destructured object than described here, but this few I chose for the scope of this article. You can read more about the useQuery hook here.

Passing variable(s) to a query function

So, what if you want to pass a variable or variables to your query function? E.g. you have a function that fetches a single post and it requires you to pass in a post id; What do you do?
Let's see how it is done.

We will a new key entry in a queryKeys.js file at ./src/util/queryKeys.js.

...
export const fetchSinglePostKey = "FETCH_SINGLE_POST";
Enter fullscreen mode Exit fullscreen mode

Create also a fetchSinglePost.service.js file at ./src/services/fetchSinglePost.service.js and create your simple async function to fetch a single post by id.

fetchSinglePost.service.js

import axios from "axios";

/**
 * @desc fetches a single post
 */
export const fetchSinglePost = async ({queryKey}) => {
const [_key, id] = queryKey
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);
  return res?.data;
};
Enter fullscreen mode Exit fullscreen mode

Post.js

import { useQuery } from "react-query";
import { fetchSinglePost } from "../../services/fetchSinglePost .service";
import { fetchSinglePostKey } from "../../util/queryKeys";

const Post = () => {

// fetching the post with the id of 1

  const {  isLoading, isError, isSuccess, refetch, remove, data, error} = useQuery([fetchSinglePostKey, 1], fetchSinglePost );


  return (
    <div>
      {isLoading ? (
        <div>Loading...</div>
      ) : isError ? (
        <div>An error while fetching post</div>
      ) : (
          <div >
            <div>{data?.title}</div>
            <div>{data?.body}</div>
          </div>
        )
      )}
    </div>
  );
};
export default Post;
Enter fullscreen mode Exit fullscreen mode

Here, we are no more using a string value for our query key but an array, passing in the query string first and the post id as required by our query function fetchSinglePost.service.js.

The fetchSinglePost function declared in useQuery hook is passed in a context, this context has queryKey array nested in it. This queryKey array contains your query string as the first item in the array and your id variable for fetching our single post.

Remember to import your Post.js component to your App.js

...

 function App() {
   return  (
            <QueryClientProvider client={queryClient}>
               ...
              <Post/>
              <ReactQueryDevtools initialIsOpen={false} />
            </QueryClientProvider>
        )
 }
Enter fullscreen mode Exit fullscreen mode

Overriding query defaults

We have seen two demonstrations using useQuery but don't forget that they all are operating within the queries default we set earlier. To override some of the global configurations, we pass an object as a third argument to the useQuery hook. Every option you declare a new value, that option is overridden for that useQuery instance only.

...
 const {  isLoading, isError, isSuccess, refetch, remove, data, error} = useQuery([fetchSinglePostKey, 1], fetchSinglePost, {
    refetchInterval : 3* 1000 //3 seconds
});

...
Enter fullscreen mode Exit fullscreen mode

What this snippet above implies is that, although we configured react-query globally to refetch queries every 30 seconds, This particular query will refetch every 3 seconds; breaking away from the global configurations.

Conclusion

The returned data from our queries are persisted in a cache. In the next part, we will discuss how the to interact with this cache.
Thank you all for your support. If you are beginner and haven't written something, do that today! Please give me a ๐Ÿ’– if this post or part of it has helped you. Comments are welcomed too.
Follow me on twitter @NnajioforEmma10

Table of contents

Discussion (0)