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
//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"
        await mongoose.connect('mongodb+srv://cairocoders:123456@cluster0.x45tgvn.mongodb.net/expressdb?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
//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
//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
//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
//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
//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
//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
//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