article

Thursday, January 30, 2025

React.js 18 Pagination with Tanstack Query | Node Express MongoDB Atlas

React.js 18 Pagination with Tanstack Query | Node Express MongoDB Atlas

https://expressjs_com/
Express JS
Fast, unopinionated, minimalist web framework for Node.js

$ npm install express --savev PS C:\nodeproject> npm install express --save
https://expressjs.com/en/starter/hello-world.html

mongoose
mongoosejs/docs/
npm install mongoose --save

cors
CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.
npmjs-com/package/cors
PS C:\nodeproject>npm i cors

run PS C:\nodeproject> node index.js
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//index.js
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
const UserModel = require('./User')
    
const app = express()
const port = 3001
   
app.use(cors())
app.use(express.json())
    
main().catch(err => console.log(err));
   
async function main() {
    try {                     //"mongodb+srv://"username":"password"@cluster0.x45tgvn.mongodb.net/"databasename"?retryWrites=true&w=majority&appName=Cluster0"
        console.log("CONNECTED TO DATABASE SUCCESSFULLY");
    } catch (error) {
        console.error('COULD NOT CONNECT TO DATABASE:', error.message);
    }
}
 
//Pagination
app.get("/pagination", async (req, res) => {
  let { page, limit } = req.query;
  if (!page) page = 1;
  if (!limit) limit = 2;
 
  const skip = (page - 1) * 2;
  const users = await UserModel.find()
    .skip(skip)
    .limit(limit);
 
  const total = await UserModel.find().countDocuments() 
  res.header('Access-Control-Expose-Headers', 'X-Total-Count')
  res.header('X-Total-Count', total)
  res.send({ page: page, limit: limit, users: users });
});
 
app.get('/', (req, res) => {
  res.send('Hello World!')
})
 
app.get('/users', (req, res) => {
    UserModel.find()
    .then(users => res.json(users))
    .catch(err => res.json(err))
})
 
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
User.js
1
2
3
4
5
6
7
8
9
10
11
12
//User.js
const mongoose = require('mongoose')
   
const UserSchema = new mongoose.Schema({
    name: String,
    email: String,
    age: Number
})
   
const UserModel = mongoose.model("users", UserSchema)
   
module.exports = UserModel;
react.dev
Create react project
Run the following command in your terminal: vite_dev/guide/
npm create vite@latest
tanstack/react-query
Hooks for fetching, caching and updating asynchronous data in React, Solid, Svelte and Vue

npm i @tanstack/react-query

react-query-devtools
npm i @tanstack/react-query-devtools

Tailwind CSS with Vite
tailwindcss_com/docs/guides/vite

Install npm i uuid

C:\react-js\my-app\src\App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//src\App.jsx
import { Route, Routes } from "react-router-dom" //npm i react-router-dom
import Users from "./pages/Users"
 
function App() {
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">React.js 18 Pagination with Tanstack Query | Node Express MongoDB Atlas</h1>
        </div>
        <Routes>
          <Route path="/" element={<Users />} />
        </Routes>
    </div>
  )
}
  
export default App
C:\react-js\my-app\src\main.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' //npm i @tanstack/react-query-devtools
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { BrowserRouter } from "react-router-dom";
import App from './App.jsx'
 
// create a client
const queryClient = new QueryClient();
 
createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <App />
        <ReactQueryDevtools initialIsOpen={false} />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
C:\react-js\my-app\src\pages\Users.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//src\pages\Users.jsx
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import LoadingSpinner from "../components/LoadingSpinner";
import Pagination from "../components/Pagination";
import UserList from "../components/UserList";
import { getUserList } from "../services/api";
 
const Users = () => {
  const [currentPage, setCurrentPage] = useState(1);
 
  const { isLoading, data, isError, isFetching, isPreviousData,  error } = useQuery({
    queryKey: ["posts", currentPage],
    queryFn: () => getUserList(currentPage),
    keepPreviousData: true
  });
 
  if (isLoading) return "Loading...";
  if (isError) return `Error: ${error.message}`;
//console.log(data.totalData);
  return (
    <div className='max-w-[1100px] mx-auto mt-10 pb-10 px-4'>
      <h1 className="text-center text-2xl my-5 underline font-bold">
        Pagination
      </h1>
      <UserList userlist={data.userlist} />
      <div className="flex items-center justify-between my-5">
        <Pagination
          currentPage={currentPage}
          totalItems={data.totalData}
          onPageChange={(page) => setCurrentPage(page)}
          isPreviousData={isPreviousData}
        />
        {isFetching ? <LoadingSpinner /> : null}
      </div>
    </div>
  );
};
 
export default Users;
C:\react-js\my-app\src\components\UserList.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//src\components\UserList.jsx
const UserList = ({ userlist }) => {
  return (
    <div className="grid grid-cols-3 gap-x-3 gap-y-3">
      {userlist.map(user => (
        <div key={user._id} className="bg-primary p-10 rounded-lg shadow-md border border-gray-200">
            <p>#id : {user._id}</p>
            <span className='block mb-4 font-bold text-lg text-secondary capitalize'>{user.name}</span>
            <span className='text-secondary'>{user.email}</span>
        </div>
      ))}
    </div>
  )
}
 
export default UserList
C:\react-js\my-app\src\components\Pagination.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//src\components\Pagination.jsx
const ITEM_PER_PAGE = 3;
const Pagination = ({
  currentPage,
  totalItems,
  onPageChange,
  isPreviousData,
}) => {
  const pageCount = Math.ceil(totalItems / ITEM_PER_PAGE);
 
  const handlePrevClick = () => onPageChange(currentPage - 1);
  const handleNextClick = () => onPageChange(currentPage + 1);
 
  return (
    <div className="btn-group">
      <button
        disabled={currentPage === 1 || isPreviousData}
        onClick={handlePrevClick}
        className="btn"
      >
        «
      </button>
      <button className="btn">Page {currentPage}</button>
      <button
        disabled={currentPage === pageCount || isPreviousData}
        onClick={handleNextClick}
        className="btn"
      >
        »
      </button>
    </div>
  );
};
 
export default Pagination;
C:\react-js\my-app\src\services\api.jsx
1
2
3
4
5
6
7
8
9
10
11
12
//src\components\api.jsx
const ITEM_PER_PAGE = 3;
export const getUserList = async (page) => {
  const response = await fetch(`http://localhost:3001/pagination?page=${page}&limit=${ITEM_PER_PAGE}`);
  const totalData = response.headers.get('X-Total-Count');
  const data = await response.json();
  console.log(totalData);
  return {
    userlist: data.users,
    totalData
  }
}
Run
C:\react-js\my-app> npm run dev

Related Post