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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//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
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\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
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
//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
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
//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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//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) {
  return response.json()
}
Pagination : 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//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
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
//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