React JS
https://react.dev/learn/start-a-new-react-project
npx create-next-app@latest
npx create-react-app@latest {project name}
Create Project
C:\react-js>npx create-react-app@latest my-app
Run
C:\react-js\my-app> npm start
Install tailwindcss https://tailwindcss.com/docs/guides/create-react-app
npm install -D tailwindcss
npx tailwindcss
init Install
react-router-dom
https://www.npmjs.com/package/react-router-dom Install axios
src\App.js
//src\App.js
import { BrowserRouter, Routes, Route } from "react-router-dom"; //npm i react-router-dom https://www.npmjs.com/package/react-router-dom
import Home from "./elements/Home";
import ViewPage from "./elements/ViewPage";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts/:slug" element={<ViewPage />} />
</Routes>
</BrowserRouter>
);
}
export default App;
src\elements\Home.js
//src\elements\Home.js
import React from 'react';
import Posts from './Posts'
import { Suspense } from "react";
import { IoSearch } from "react-icons/io5"; //https://www.npmjs.com/package/react-icons npm install react-icons --save
function Home() {
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 WordPress Rest API Pagination with Featured Image and View Single Page | Tailwind CSS</h1>
</div>
<div className="overflow-x-auto py-10">
<div className="mb-2 w-full text-right">
<div className="relative flex flex-1">
<input
type="text"
className="w-full border border-gray-200 py-2 pl-10 text-sm outline-2 rounded-sm"
placeholder="Search..."
/>
<IoSearch className="absolute left-3 top-2 h-5 w-5 text-gray-500" />
</div>
</div>
<Suspense fallback="Loading...">
<Posts/>
</Suspense>
</div>
</div>
)
}
export default Home
src\elements\Posts.js
//src\elements\Posts.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';
export default function Posts() {
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const perPage = 3;
useEffect(() => {
let url = `http://localhost:8888/cairocoders/wp-json/wp/v2/posts?per_page=${perPage}&page=${currentPage}&_embed`;
axios.get(url).then((res) => {
const { data, headers } = res;
setTotalPages(Number(headers['x-wp-totalpages']));
setPosts(data);
//console.log("Headers", headers['x-wp-totalpages']);
});
}, [currentPage])
//console.log('posts', posts);
return (
<div>
<div className="grid md:grid-cols-3 gap-5 mt-10">
{posts.map((post, index) => (
<div key={index} 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={`/posts/${post.slug}`}>
{post.title.rendered}
</a>
</h1>
</div>
</div>
))}
</div>
{ posts.length > 0 && (
<div className='w-1/2 py-10 m-auto flex justify-between items-center align-middle flex-wrap gap-10'>
<button className='btn-primary p-2 bg-blue-500 text-white text-lg rounded-lg hover:shadow-lg disabled:opacity-50'
disabled={currentPage === 1}
onClick={() => setCurrentPage(currentPage - 1)}
>
Previous
</button>
<span className='text-lg'>{currentPage} of {totalPages}</span>
<button className='btn-primary p-2 bg-blue-500 text-white text-lg rounded-lg hover:shadow-lg disabled:opacity-50'
disabled={currentPage === totalPages}
onClick={() => setCurrentPage(currentPage + 1)}
>
Next
</button>
</div>
)
}
</div>
);
}
src\elements\ViewPage.js
//src\elements\ViewPage.js
import axios from 'axios';
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom';
const Single = () => {
const { slug } = useParams();
const [post, setPost] = useState({});
useEffect(()=>{
// axios
let url = "http://localhost:8888/cairocoders/wp-json/wp/v2/posts?_embed&slug="+slug;
axios.get(url).then(res => {
console.log('res', res);
//console.log(res.data[0].id);
setPost(res.data);
}).catch(err => {
console.log('error:', err.message);
});
}, []);
return (
<div className="min-h-screen flex items-center justify-center bg-slate-100">
<div className="bg-white rounded-sm shadow p-8">
{
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 Single;
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;
}
Run C:\react-j\my-app>npm start http://localhost:3000/
