article

Sunday, March 16, 2025

Reactjs 18 WordPress Rest API Pagination View Post | Tanstack Query Tailwind CSS

Reactjs 18 WordPress Rest API Pagination View Post | Tanstack Query Tailwind CSS

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

my-app\src\App.jsx
//src\App.jsx
import { Route, Routes } from "react-router-dom" //npm i react-router-dom
import Home from "./pages/Home"
import Post from "./pages/Post"

function App() { 
  return (
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/post/:slug" element={<Post />} />
    </Routes>
  )
}
   
export default App
my-app\src\main.jsx
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
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 />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
Home Page : my-app\src\pages\Home.jsx
//src\pages\Home.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { getPostList } from "../api/post";
import Pagination from "../components/Pagination";
import { useSearchParams } from "react-router-dom"
import PostList from "./PostList";

export default function Home() {
    const [searchParams] = useSearchParams();
    const page = Number(searchParams.get("page"));
    //console.log(page)
  
    const currentPage = Number(page) || 1;
    //console.log(currentPage)

    const { isLoading, data, isError,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getPostList(currentPage)
    });
    //console.log(data);

    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;

    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    console.log(totalPages);
  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 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Reactjs 18 WordPress Rest API Pagination View Post | Tanstack Query Tailwind CSS</h1>
        </div> 
        <div className="overflow-x-auto pt-10">
            <PostList postlists={data.postlist} />
            <div className="flex items-center justify-between my-5">
                <Pagination totalPages={totalPages}/>
            </div>
        </div>
    </div>
  );
}
Post List page : my-app\src\pages\PostList.jsx
//my-app\src\pages\PostList.jsx
const PostList = ({ postlists }) => { 
    return (
    <>
      <div className="grid md:grid-cols-3 gap-5 mt-10">
        {postlists.map((post) => (
          <div key={post.id} className="max-w-sm border border-gray-200 rounded-md shadow">
            <div className="relative aspect-video">
              <img src={post._embedded["wp:featuredmedia"][0].media_details.sizes.full.source_url}
                alt={post.title.rendered}
                sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
                className="rounded-t-md object-cover"/>
            </div>
            <div className="p-5">
              <h1>
                <a
                  href={`/post/${post.slug}`}>
                  {post.title.rendered}
                </a>         
              </h1>
            </div>
          </div>
        ))}
      </div>
    </>
  );
};
  
export default PostList;
View page : my-app\src\pages\Post.jsx
//my-app\src\pages\Post.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useParams } from "react-router-dom";
import { fetchPost } from "../api/post";
  
const Read = () => {
    const { slug } = useParams();
    console.log(slug);
 
    const {
        isLoading,
        isError,
        data: post,
        error,
    } = useQuery({
        queryKey: ["posts", slug],
        queryFn: () => fetchPost(slug),
    });
    console.log(post);
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
  
  return (
    <div className="min-h-screen flex items-center justify-center bg-slate-100">
      <div className="bg-white rounded-sm shadow p-8 text-black">
            {
                post.length ? (
                <div>
                  <h1 className="text-2xl font-bold mb-5">{post[0].title.rendered}</h1>
                    <div className="mb-4">            
                      <div 
                        dangerouslySetInnerHTML={{ __html: post[0]['content']['rendered'] }}
                      />   
                    </div>
                </div>
                ) : ('Loading....')
              
            }
      </div>
    </div>
  )
}
  
export default Read
Api : my-app\src\api\post.jsx
//src\api\post.jsx
const ITEM_PER_PAGE = 6;
export async function getPostList(page) {
  const response = await fetch(`http://localhost:8888/cairocoders/wp-json/wp/v2/posts?per_page=${ITEM_PER_PAGE}&page=${page}&_embed`);
  const totalData = response.headers.get('X-WP-Total');
  const data = await response.json();
  console.log(data);
  return {
    postlist: data,
    totalpage: totalData,
    perpage: ITEM_PER_PAGE
  }
}
 
export async function fetchPost(slug) {
  const response = await fetch("http://localhost:8888/cairocoders/wp-json/wp/v2/posts?_embed&slug="+slug);
  return response.json()
}
Pagination : my-app\src\components\Pagination.jsx
//src\components\Pagination.jsx
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
import { useSearchParams, useLocation } from "react-router-dom" 

export const generatePagination = (currentPage, totalPages) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
   
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
  
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
  
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};

const Paginationnumber = ({ totalPages }) => {
  let location = useLocation(); 
  console.log(location);
  const { hash, pathname, search } = location;
  //console.log(pathname);

  const [searchParams] = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  //console.log(currentPage); 2

  const allPages = generatePagination(currentPage, totalPages);

  const createPageURL = (pageNumber) => {
      const params = searchParams;
      params.set("page", pageNumber.toString());
      return `${pathname}?${params.toString()}`;
  };

  const PaginationNumber = ({
      page,
      href,
      position,
      isActive,
  }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "rounded-l-sm": position === "first" || position === "single",
              "rounded-r-sm": position === "last" || position === "single",
              "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
              "hover:bg-blue-700": !isActive && position !== "middle",
              "text-gray-300 pointer-events-none": position === "middle",
          }
      );

      return isActive && position === "middle" ? (
          <div className={className}>{page}</div>
      ) : (
          <a href={href} className={className}>
              {page}
          </a>
      );
  };

  const PaginationArrow = ({ href, direction, isDisabled }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "pointer-events-none text-blue-300": isDisabled,
              "hover:bg-blue-700": !isDisabled,
              "mr-2": direction === "left",
              "ml-2": direction === "right",
          }
      );

      const icon =
        direction === "left" ? (
          <HiChevronLeft size={20} />
        ) : (
          <HiChevronRight size={20} />
        );

    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <a href={href} className={className}>
        {icon}
      </a>
    );
  };

  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position;

          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";

          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page} 
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};

export default Paginationnumber;
Run
C:\react-js\my-app> npm run dev

themes\cairocoders\functions.php
//themes\cairocoders\functions.php
<?php
/**
 * Theme functions and definitions
 *
 * @package cairocoders
 */

add_action('rest_api_init', 'register_rest_images' );

function register_rest_images(){
    register_rest_field( array('post'),
        'fimg_url',
        array(
            'get_callback'    => 'get_rest_featured_image',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}

function get_rest_featured_image( $object, $field_name, $request ) {
    if( $object['featured_media'] ){
        $img = wp_get_attachment_image_src( $object['featured_media'], 'app-thumb' );
        return $img[0];
    }
    return false;
}

Related Post