ReactJs is a rich UI development programming platform.
The post is using WordPress WP-JSON REST API without using any third party NPM module. One of the popular NPM modules is WPAPI which you can use for advanced development.
In this tutorial, we’re going to build a React blog template example with WP-JSON, Axios, and Bootstrap. I will show you:
- React template rendering
- Axios module to fetch remote data
- WP-JSON REST API access
Let’s start.
1. Directory Structure
2. Template Parts
a. Header Part
src/parts/header.js
import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import HeaderNavigation from './navigation'; import SearchBar from './searchbar'; class Header extends Component { render(){ return ( <header> <nav className="navbar navbar-expand-md navbar-light bg-white absolute-top"> <div className="container"> <button className="navbar-toggler order-2 order-md-1" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbar-left navbar-right" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> <div className="collapse navbar-collapse order-3 order-md-2" id="navbar-left"> <HeaderNavigation/> </div> <Link to={'/'} className="navbar-brand mx-auto order-1 order-md-3">React Blog</Link> <SearchBar/> </div> </nav> </header> ) } }; export default Header;
b. Footer Part
src/parts/footer.js
import React, { Component } from "react"; class Footer extends Component{ render(){ return( <footer className="site-footer bg-darkest"> <div className="container"> <div className="copy"> © Boot 2019<br /> All rights reserved </div> </div> </footer> ) } }; export default Footer;
c. Navigation part
src/parts/navigation.js
import React, { Component } from 'react'; import {Link} from 'react-router-dom'; class HeaderNavigation extends Component { render(){ return( <ul className="navbar-nav mr-auto"> <li className="nav-item"> <Link className="nav-link active" to="/">Home</Link> </li> <li className="nav-item"> <Link className="nav-link active" to="/blog">Blog</Link> </li> <li className="nav-item"> <Link className="nav-link" to="/create">Create Post</Link> </li> </ul> ) } }; export default HeaderNavigation;
d. Sidebar Part
src/parts/sidebar.js
import React, { Component } from "react"; class Sidebar extends Component{ render(){ return( <div className="col-md-3 ml-auto"> <aside className="sidebar"> <div className="card mb-4"> <div className="card-body"> <h4 className="card-title">About</h4> <p className="card-text">Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. </p> </div> </div> </aside> </div> ) } }; export default Sidebar;
e. Searchbar Part
src/parts/searchbar.js
import React, { Component } from "react"; class SearchBar extends Component{ render(){ return( <div className="collapse navbar-collapse order-4 order-md-4" id="navbar-right"> <ul className="navbar-nav ml-auto"> <li className="nav-item"> <a className="nav-link" href="page-about.html">Sign In</a> </li> <li className="nav-item"> <a className="nav-link" href="page-contact.html">Sign Up</a> </li> </ul> <form className="form-inline" role="search"> <input className="search js-search form-control form-control-rounded mr-sm-2" type="text" title="Enter search query here.." placeholder="Search.." aria-label="Search"/> </form> </div> ) } }; export default SearchBar;
f. Main content area
src/parts/content.js
This file a little bit complex right now. Because we yet do not discuss the components. Keep going. You get it. 😃
import React, { Component } from "react"; import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'; import PostCategories from '../components/category-listing'; import PostListing from '../components/post-listing'; import CategoryDetail from '../components/category-detail'; import PostDetail from '../components/post-detail'; import Error from '../components/error-page'; class Content extends Component{ render(){ return( <div className="col-md-9"> <Switch> <Route exact path={['/', '/categories']} component={PostCategories} /> <Route exact path={["/category/:slug", "/category/:slug/page/:page"]} component={CategoryDetail} /> <Route exact path={["/blog", "/blog/page/:page"]} component={PostListing} /> <Route exact path={['/404']} component={Error} /> <Route exact path="/:slug" component={PostDetail} /> </Switch> </div> ) } }; export default Content;
3. Axios HTTP call
root/http-common.js
import axios from 'axios'; export default axios.create({ baseURL: 'http://narindersingh.in/wp-json', headers: { "Content-type": "application/json" } });
4. Blog service class
src/services/blog.service.js
import http from '../http-common'; class BlogDataService{ getCategoryList(){ return http.get('/wp/v2/categories'); } getCategoryDetail(slug){ return http.get(`/wp/v2/categories?slug=${slug}`); } getCategoryPosts(cat_id,page){ return http.get(`/wp/v2/posts?categories=${cat_id}&per_page=2&page=${page}`); } getPostList(page){ return http.get(`/wp/v2/posts/?per_page=2&page=${page}`); } getPostDetail(slug){ console.log(slug); return http.get(`/wp/v2/posts/?slug=${slug}`); } getPostFeaturedImage(id){ return http.get(`/wp/v2/media/${id}`); } getPostCategoryName(id){ return http.get(`/wp/v2/categories/${id}`) } } export default new BlogDataService();
5. Landing page and blog categories list
src/components/category-listing.js
import React, {Component, Fragment} from 'react'; import {Link} from 'react-router-dom'; import BlogDataService from '../services/blog.service'; class PostCategoryListing extends Component{ constructor(props){ super(props); this.categoryListing = this.categoryListing.bind(this); this.state = { loading: false, message: '', categories: [] }; } componentDidMount(){ this.categoryListing(); } categoryListing(){ BlogDataService.getCategoryList() .then( response => { console.log(response.data); this.setState({ categories: response.data, loading: false }); }, error => { //console.log(error); this.setState({ loading:false, message: (error.response && error.response.data && error.response.data.message ) || error.message || error.toString() }); } ).catch(e => { console.log(e); }); } render(){ const { categories } = this.state; return( <Fragment> <h1>Blog Post Categories</h1> <div className="container-fluid"> <div className="row"> { categories && categories.map( ( category, index) => ( <div className="col-xs-12 col-md-6" key={index}> <h5 className="mt-5"> <Link to={"/category/"+category.slug} key={index}> { category.name } </Link> </h5> </div> )) } </div> </div> </Fragment> ) } }; export default PostCategoryListing;
6. Blog post listing
src/components/post-listing.js
import React, { Component } from "react"; import { Link } from 'react-router-dom'; import ReactHtmlParser from 'react-html-parser'; import Moment from 'react-moment'; import BlogDataService from '../services/blog.service'; import PostFeaturedImage from './post-featured-image'; import PostCategoryName from './post-category-name'; import PostPagination from './post-pagination'; class PostListing extends Component{ constructor(props){ super(props); this.blogPostListing = this.blogPostListing.bind(this); this.state = { blogPosts: [], headers: [], currentPage: 1 }; } componentDidUpdate(prevProps, prevState){ if (prevProps.match.params.page !== this.props.match.params.page) { this.blogPostListing(this.props.match.params.page); } } componentDidMount(){ let page = this.props.match.params.page ? this.props.match.params.page : 1; this.blogPostListing(page); } blogPostListing(page) { BlogDataService.getPostList(page) .then(response => { this.setState({ blogPosts: response.data, headers: response.headers, currentPage: page }); }, error => { this.setState({ message: (error.response && error.response.data && error.response.data.message) || error.message || error.toString() }) }).catch(e => { console.log(e); }); } render() { const { blogPosts,currentPage,headers } = this.state; return( <div> { blogPosts && blogPosts.map( (blogPost, index) => ( <article className="card mb-4" key={index}> <header className="card-header"> <div className="card-meta"> <time className="timeago badge" dateTime={blogPost.date}> <Moment format="D MMMM YYYY">{blogPost.date}</Moment> </time> in { blogPost.categories.map( ( category_id, catindex) => ( <PostCategoryName id={category_id} key={catindex}/> ) )} </div> <Link to={'/'+blogPost.slug}> <h4 className="card-title">{blogPost.title.rendered}</h4> </Link> </header> { blogPost.featured_media > 0 && <Link to={'/'+blogPost.slug}> <PostFeaturedImage id={blogPost.featured_media}/> </Link> } <div className="card-body"> <div className="card-text">{ ReactHtmlParser(blogPost.excerpt.rendered) }</div> </div> </article> ) ) } { blogPosts.length > 0 && <PostPagination headers={headers} activePage={currentPage} activeUrl="blog"/> } </div> ) } }; export default PostListing;
7. Post category name, post featured image, and post date render in post listing
src/components/post-featured-image.js
import React, {Component} from 'react'; import BlogDataService from '../services/blog.service' class PostFeaturedImage extends Component{ constructor(props){ super(props); this.postFeaturedImage = this.postFeaturedImage.bind(this); this.state = { postFeaturedImg: [], message: '' } } componentDidUpdate(prevProps, prevState){ if (prevProps.id !== this.props.id) { this.postFeaturedImage(this.props.id); } } componentDidMount(){ this.postFeaturedImage(this.props.id); } postFeaturedImage(id){ BlogDataService.getPostFeaturedImage(id) .then( response => { //console.log(response.data); this.setState({ postFeaturedImg: response.data }); }, error => { this.setState({ message: (error.response && error.response.data && error.response.data.message) || error.message || error.toString() }); } ) } render(){ const {postFeaturedImg} = this.state; return( <img className="card-img" src={postFeaturedImg.source_url} alt={postFeaturedImg.alt_text} /> ) } } export default PostFeaturedImage;
src/components/post-category-name.js
import React, {Component} from 'react'; import { Link } from 'react-router-dom'; import BlogDataService from '../services/blog.service' class PostCategoryName extends Component{ constructor(props){ super(props); this.postCategoryName = this.postCategoryName.bind(this); this.state = { categoryName: [], message: '' } } componentDidUpdate(prevProps, prevState){ if (prevProps.id !== this.props.id) { this.postCategoryName(this.props.id); } } componentDidMount(){ this.postCategoryName(this.props.id); } postCategoryName(id){ BlogDataService.getPostCategoryName(id) .then( response => { //console.log(response.data); this.setState({ categoryName: response.data }); }, error => { this.setState({ message: (error.response && error.response.data && error.response.data.message) || error.message || error.toString() }); } ) } render(){ const {categoryName} = this.state; return( <Link className="badge" to={'/category/'+categoryName.slug}>{categoryName.name}</Link> ) } } export default PostCategoryName;
8. Post pagination
We can use any NPM module for pagination. But to make the tutorial easy to understand. I build a custom component.
src/components/post-pagination.js
import React, {Component} from 'react'; import { Link } from 'react-router-dom'; class PostPagination extends Component{ constructor(props){ super(props); //this.displayPaginationLink = this.displayPaginationLink.bind(this); this.state = { headers: [], activePage: 1, activeUrl: 'blog' } } componentDidUpdate(prevProps, prevState){ if (prevProps.activePage !== this.props.activePage) { this.setState({ headers: this.props.headers, activePage: this.props.activePage, activeUrl:this.props.activeUrl }); } } componentDidMount(){ this.setState({ headers: this.props.headers, activePage: this.props.activePage, activeUrl:this.props.activeUrl }); } render(){ const { headers,activePage,activeUrl } = this.state; console.log(this.state); let pagination = []; let pageIndex = 0; let numberPages = headers['x-wp-totalpages']; let currentPage = parseInt(activePage); if(currentPage > 2 ){ pagination[pageIndex] = <li className="page-item" key={pageIndex}> <Link className="page-link" to={`/${activeUrl}/page/1`}>First</Link> </li>; pageIndex++; } if(currentPage > 1 ){ let prevPage = parseInt(currentPage) - 1; pagination[pageIndex] = <li className="page-item" key={pageIndex}> <Link className="page-link" to={`/${activeUrl}/page/${prevPage}`}>Previous</Link> </li>; pageIndex++; } else { pagination[pageIndex] = <li className="page-item disabled" key={pageIndex}> <span className="page-link">Previous</span> </li>; pageIndex++; } if(numberPages <= 5 ){ for(let page = 1; page <= numberPages; page++){ pagination[pageIndex] = <li className={(page === currentPage) ? 'page-item active' : 'page-item'} key={pageIndex}> <Link className="page-link" to={`/${activeUrl}/page/${page}`}>{page}</Link> </li>; pageIndex++; } } if(numberPages > 5 ){ let counter = 0; let startPageRange = (numberPages - currentPage ) > 5 ? currentPage : numberPages-5; let pageRange = parseInt(currentPage)+5 > numberPages ? numberPages : parseInt(currentPage)+5; for(let page = startPageRange; page <= pageRange; page++){ if( counter === 2 && pageRange > 5){ pagination[pageIndex] =<li className="page-item disabled" key={pageIndex}> <span className="page-link">...</span> </li>; pageIndex++; } else { pagination[pageIndex] = <li className={(page === currentPage) ? 'page-item active' : 'page-item'} key={pageIndex}> <Link className="page-link" to={`/${activeUrl}/page/${page}`}>{page}</Link> </li>; pageIndex++; } counter++; } } if(currentPage < numberPages ){ let nextPage = parseInt(currentPage) + 1; pagination[pageIndex] = <li className="page-item" key={pageIndex}> <Link className="page-link" to={`/${activeUrl}/page/${nextPage}`}>Next</Link> </li>; pageIndex++; }else{ pagination[pageIndex] = <li className="page-item disabled" key={pageIndex}> <span className="page-link">Next</span> </li>; pageIndex++; } if(currentPage < numberPages && numberPages >= 5 ){ pagination[pageIndex] = <li className="page-item" key={pageIndex}> <Link className="page-link" to={`/${activeUrl}/page/${numberPages}`}>Last</Link> </li>; pageIndex++; } return( <nav> { this.state.headers['x-wp-totalpages'] > 1 && <ul className="pagination justify-content-center"> { pagination } </ul> } </nav> ) } } export default PostPagination;
9. Post detail page
src/components/post-detail.js
import React, {Component, Fragment} from 'react'; import { Link, Redirect } from 'react-router-dom'; import ReactHtmlParser from 'react-html-parser'; import Moment from 'react-moment'; import BlogDataService from '../services/blog.service'; import PostFeaturedImage from './post-featured-image'; import PostCategoryName from './post-category-name'; class PostDetail extends Component{ constructor(props){ super(props); this.getPostDetail = this.getPostDetail.bind(this); this.state = { blogPost: [], message: '', redirect: null } } componentDidMount(){ this.getPostDetail(this.props.match.params.slug); } getPostDetail(slug){ BlogDataService.getPostDetail(slug) .then(response => { console.log(response); if(response.data.length > 0){ this.setState({ blogPost: response.data[0] }); }else{ this.setState({ redirect: '404' }); } }, error => { this.setState({ message: (error.response && error.response.data && error.response.data.message) || error.message || error.toString() }) }) .catch(err => { console.log(err); }) } render(){ const {blogPost} = this.state; if (this.state.redirect) { return <Redirect to={this.state.redirect} /> } return( <Fragment> { blogPost.id > 0 && <article className="card mb-4"> <header className="card-header"> <div className="card-meta"> <time className="timeago badge" dateTime={blogPost.date}> <Moment format="D MMMM YYYY">{blogPost.date}</Moment> </time> in { blogPost.categories.map( ( category_id, catindex) => ( <PostCategoryName id={category_id} key={catindex}/> ) )} </div> <Link to={'/'+blogPost.slug}> <h4 className="card-title">{blogPost.title.rendered}</h4> </Link> </header> { blogPost.featured_media > 0 && <Link to={'/'+blogPost.slug}> <PostFeaturedImage id={blogPost.featured_media}/> </Link> } <div className="card-body"> <div className="card-text">{ ReactHtmlParser(blogPost.content.rendered) }</div> </div> </article> } </Fragment> ) } } export default PostDetail;
10. Category detail page
Display category related posts and category description.
src/components/category-detail.js
import React, {Component, Fragment} from 'react'; //import {Link} from 'react-router-dom'; import BlogDataService from '../services/blog.service'; import CategoryPosts from './category-posts'; class CategoryDetail extends Component{ constructor(props){ super(props); this.categoryDetail = this.categoryDetail.bind(this); this.state = { loading: false, message: '', categoryContent: [], subCategories: [], currentPage: 1 } } componentDidUpdate(prevProps, prevState){ if (prevProps.match.params.slug !== this.props.match.params.slug || prevProps.match.params.page !== this.props.match.params.page){ let page = this.props.match.params.page ? this.props.match.params.page : 1; this.categoryDetail(this.props.match.params.slug, page); } } componentDidMount(){ let page = this.props.match.params.page ? this.props.match.params.page : 1; this.categoryDetail(this.props.match.params.slug, page); } categoryDetail(slug, currentPage) { BlogDataService.getCategoryDetail(slug) .then( response => { console.log(response.data); this.setState({ categoryContent: response.data[0], currentPage: currentPage }); }, error => { this.setState({ message: (error.response && error.response.data && error.response.data.message) || error.message || error.toString() }) } ) } render(){ const {categoryContent, currentPage} = this.state; console.log(currentPage); return( <Fragment> <div className="jumbotron"> <h1 className="display-4">{ categoryContent.name }</h1> <div className="lead"> {categoryContent.description} </div> </div> { categoryContent.id > 0 && <CategoryPosts catId={categoryContent.id} catgorySlug={categoryContent.slug} currentPage={currentPage}/> } </Fragment> ) } } export default CategoryDetail;
src/components/category-posts.js
import React, {Component, Fragment} from 'react'; import { Link } from 'react-router-dom'; import ReactHtmlParser from 'react-html-parser'; import Moment from 'react-moment'; import BlogDataService from '../services/blog.service'; import PostFeaturedImage from './post-featured-image'; import PostCategoryName from './post-category-name'; import PostPagination from './post-pagination'; class CategoryPosts extends Component{ constructor(props){ super(props); this.categoryPosts = this.categoryPosts.bind(this); this.state = { blogPosts: [], message: '', headers: [], currentPage: 1, catgorySlug: '' } } componentDidUpdate(prevProps, prevState){ if (prevProps.catId !== this.props.catId || prevProps.currentPage !== this.props.currentPage) { let page = this.props.currentPage ? this.props.currentPage : 1; this.categoryPosts(this.props.catId, this.props.catgorySlug, page); } } componentDidMount(){ let page = this.props.currentPage ? this.props.currentPage : 1; this.categoryPosts(this.props.catId, this.props.catgorySlug, page); } categoryPosts(cat_id, catgorySlug, currentPage){ //console.log(cat_id); BlogDataService.getCategoryPosts(cat_id, currentPage ) .then( response => { //console.log(response.data); this.setState({ blogPosts: response.data, headers: response.headers, currentPage: currentPage, catgorySlug: catgorySlug }); }, error => { this.setState({ message: (error.response && error.response.data && error.response.data.message) || error.message || error.toString() }) } ).catch(e => { console.log(e); }); } render(){ const { blogPosts,currentPage,headers,catgorySlug } = this.state; console.log(currentPage); return( <Fragment> { blogPosts && blogPosts.map( (blogPost, index) => ( <article className="card mb-4" key={index}> <header className="card-header"> <div className="card-meta"> <time className="timeago badge" dateTime={blogPost.date}> <Moment format="D MMMM YYYY">{blogPost.date}</Moment> </time> in { blogPost.categories.map( ( category_id, catindex) => ( <PostCategoryName id={category_id} key={catindex}/> ) )} </div> <Link to={'/'+blogPost.slug}> <h4 className="card-title">{blogPost.title.rendered}</h4> </Link> </header> { blogPost.featured_media > 0 && <Link to={'/'+blogPost.slug}> <PostFeaturedImage id={blogPost.featured_media}/> </Link> } <div className="card-body"> <div className="card-text">{ ReactHtmlParser(blogPost.excerpt.rendered) }</div> </div> </article> ))} { blogPosts.length > 0 && <PostPagination headers={headers} activePage={currentPage} activeUrl={`category/${catgorySlug}`}/> } </Fragment> ) } } export default CategoryPosts;
11. The root App.js code
root/App.js
import React from 'react'; //import logo from './logo.svg'; import {BrowserRouter as Router } from 'react-router-dom'; import 'bootstrap/dist/css/bootstrap.min.css'; import './App.css'; import Header from "./parts/header"; import Sidebar from './parts/sidebar'; import Footer from './parts/footer'; import Content from './parts/content'; function App() { return ( <Router> <div className="App"> <Header/> <main className="main pt-4"> <div className="container"> <div className="row"> <Content/> <Sidebar/> </div> </div> </main> <Footer/> </div> </Router> ); } export default App;
Required NPM modules
- Bootstrap
- Axios
- Date-fns
- Moment
Access full source code
How to run on the localhost server?
- copy code in your application directory
- open your system command prompt. Keep in mind the command prompt current directory should be application root.
- Run npm install
- After all NPM modules installation
- Run npm start command.