Hi there!!! How you doing? Everthing all right? I hope so!
Have been a long time where I don't write some article, but today I want to introduce you this amazing tool, called React query.
If you don't know or if you even listened about React Query, I will explain in these article.
React Query is a state management, but with a lot of features that you can use. For example, offline support, pre-fetching, caching, pagination, mutations, infinite scroll, auto refetching, backend agnotistic and a lot of more of features.
I want to create two articles to introduce you the main features that I use on my daily life and personal projects.
This first artile let's see how to configure in our project, fetch data, pagination, auto caching and pre-fetching.
We will be using Vite with React and Typescript, Tailwind for some styling.
First, let's create our project using the command:
yarn create vite react-query-demo --template react-ts
Then, let's open the project on terminal and install the dependencies using
yarn
or if you are using npm, just use npm install
Now, let's add the react-query dependency using:
yarn add react-query
Also, adding the tailwind:
yarn add -D tailwindcss postcss autoprefixer
Creating the setup files of tailwind:
npx tailwindcss init -p
And in our tailwind.config.js created, let's add:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
And in our index.css let's clean and remove all the defaut styles and add the tailwind directives:
index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
and that's it! Now we can start our demo project!
I will clean the project, removing the App.css.
Now let's begin to code!!!
First let's clean the App.tsx
and let's just leave the component function.
function App() {
return <></>
}
And in our main.tsx
let's add the react query provider:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import App from "./App.tsx";
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>
);
The Query Client provider is the React Query Provider and we need pass the client as property. So we instantiate our client with the new Query Client()
.
Notice the React Query have the DevTools himself. And it's great and awesome!!!
Just add inside the provider the ReactQueryDevtools
and passing the initialIsOpen
property as false
because every update the dev tool it will be opened.
Now you can start your application using:
yarn dev
And let's see:
This flower is our dev tools of react query. We can use to analise the status of all of our requests, caching and a lot of more. (This dev tools 'flower' it's only available on development mode. In the production mode it not will be rendered).
Back to the App.tsx
let's add the call of our Fake API. I will be using the json placeholder. Let's on top of the component function App(){}
We can use the pagination passing the _limit
and _page
as query parameter on URL.
const MAX_POST_PAGE = 5;
const fetchPosts = async (pageNumber: number) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=${MAX_POST_PAGE}&_page=${pageNumber}`
);
const posts = (await response.json()) as Post[];
return posts;
};
For example:
If you access the: https://jsonplaceholder.typicode.com/posts?_limit=5&_page=1
We will have 5 posts on page 1.
Now let's add some state to represent current page itself inside of our component:
const [currentPage, setCurrentPage] = useState(1);
And let's call the react query hook:
const { data, isError, isLoading } = useQuery(
["posts", currentPage],
() => fetchPosts(currentPage),
{
staleTime: 0,
keepPreviousData: true,
}
);
Notice that already are using the pagination. We need to pass some unique key, called 'posts' and the current page on tuple. On the second paramenter let's call the function to fetch the posts passing the current page.
And on third parameter we can pass some options for example:
staleTime
the time that data it will be available until the next update. keepPreviousData
, will keep the previous data on pagination, avoiding refetch. And a lot of more properties that you can see on documentation.
Now, we will create our component:
if (isError) return <>Error</>;
if (isLoading) return <>Loading...</>;
return (
<>
<ul>
{data?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<div className="w-screen flex justify-around items-center">
<button
className="border bg-blue-600 hover:bg-blue-400 text-white rounded-md w-24 h-12"
disabled={currentPage <= 0}
onClick={() => setCurrentPage(currentPage - 1)}
>
Previous
</button>
<p className="font-semibold">Page {currentPage}</p>
<button
className="border bg-blue-600 hover:bg-blue-400 text-white rounded-md w-24 h-12"
onClick={() => setCurrentPage(currentPage + 1)}
>
Next
</button>
</div>
</>
);
The useQuery function will provide a lot of status of our data fetch, like isLoading, data itself (response), isFetching, error, isError and a lot of more status that you can use.
Notice that if isLoading
, I'm rendering the a element with is loading...
If has an error, I'm rendering the error message.
And if success I'm listing the data with our buttons next
and previous. This buttons will increment or decrease the currentPage
state.
Let's see how the application seems:
And Woooow we have our pagination! Easy Peasy!
To Finish let's add the Pre-Fetching
!
Let's add another hook called useQueryClient
;
const queryClient = useQueryClient();
And create inside of an useEffect:
useEffect(() => {
if (currentPage < MAX_POST_PAGE) {
const nextPage = currentPage + 1;
queryClient.prefetchQuery([
"posts",
nextPage,
() => fetchPosts(nextPage),
]);
}
}, [currentPage, queryClient]);
The queryClient will be pre-fetching the next data of our page. So we improved the user experience of our application, getting the next page and the loader will not be rendered, because always we have the next page.
And showing the entire component:
import { useEffect, useState } from "react";
import { Post } from "./types";
import { useQuery, useQueryClient } from "react-query";
const MAX_POST_PAGE = 5;
const fetchPosts = async (pageNumber: number) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=${MAX_POST_PAGE}&_page=${pageNumber}`
);
const posts = (await response.json()) as Post[];
return posts;
};
function App() {
const [currentPage, setCurrentPage] = useState(1);
// pre-fetching
const queryClient = useQueryClient();
useEffect(() => {
if (currentPage < MAX_POST_PAGE) {
const nextPage = currentPage + 1;
queryClient.prefetchQuery([
"posts",
nextPage,
() => fetchPosts(nextPage),
]);
}
}, [currentPage, queryClient]);
const { data, isError, isLoading } = useQuery(
["posts", currentPage],
() => fetchPosts(currentPage),
{
staleTime: 0,
keepPreviousData: true,
}
);
if (isError) return <>Error</>;
if (isLoading) return <>Loading...</>;
return (
<>
<ul>
{data?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<div className="w-screen flex justify-around items-center">
<button
className="border bg-blue-600 hover:bg-blue-400 text-white rounded-md w-24 h-12"
disabled={currentPage <= 0}
onClick={() => setCurrentPage(currentPage - 1)}
>
Previous
</button>
<p className="font-semibold">Page {currentPage}</p>
<button
className="border bg-blue-600 hover:bg-blue-400 text-white rounded-md w-24 h-12"
onClick={() => setCurrentPage(currentPage + 1)}
>
Next
</button>
</div>
</>
);
}
export default App;
And the final result of our application:
Amazing, isnt'it? Easy Peasy to use, and the React Query will provide amazing features.
In the next article I will explain the Infine Scroll
, for example, any social medias use as feature. Is some pagination, but with scrolling, without buttons.
So I see you on the second article of React Query! I see you soon!
I hope you liked of this content!
Thank you so much, folks!
My Social Medias:
Linkedin: https://www.linkedin.com/in/kevin-uehara/
Instagram: https://www.instagram.com/uehara_kevin/
Twitter: https://twitter.com/ueharaDev
Github: https://github.com/kevinuehara
dev.to: https://dev.to/kevin-uehara
YouTube: https://www.youtube.com/@ueharakevin/
Top comments (2)
That's great article. Thanks you very much!!. useInfiniteQuery doesn't work well, lol
wow that's awesome