Hoàn thiện một ví dụ ReactJs Wordpress (ok)

C:\xampp\htdocs\abc\wp-content\themes\apps\package.json

{
  "name": "apps",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "^4.11.0",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "bootstrap": "^4.5.2",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "3.4.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

C:\xampp\htdocs\abc\wp-content\themes\apps\index.php

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="icon" href="/wp-content/themes/apps/favicon.ico" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="/wp-content/themes/apps/logo192.png" />
  <link rel="manifest" href="/wp-content/themes/apps/manifest.json" />
  <title>React App</title>
</head>
<body>
	<noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>
</html>

C:\xampp\htdocs\celestial\wp-content\themes\celestial\functions.php

<?php
/**
 * Enqueue scripts and styles.
 *
 * @since Celestial 1.0
 */
function celestial_scripts() {
	// Load our main stylesheet.
	wp_enqueue_style( 'bootstrap-style', 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css' );
	wp_enqueue_style( 'celestial-style-dist', get_template_directory_uri() . '/dist/style.css');
	wp_enqueue_style( 'celestial-style', get_template_directory_uri() . '/style.css' );
    // Load scripts
	//wp_enqueue_script( 'jquery', 'https://code.jquery.com/jquery-3.2.1.slim.min.js', '20171006', false );	
	wp_enqueue_script( 'scrollmagic', 'https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.5/ScrollMagic.min.js' , array( 'jquery' ), '1.0', false );    
	//wp_enqueue_script( 'popper', 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js', array( 'jquery' ), '20171006', false );
    //wp_enqueue_script( 'bootstrap-script', 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js', array( 'jquery' ), '20171006', false );
    wp_enqueue_script( 'celestial-script', get_template_directory_uri() . '/dist/app.js' , array(), '1.0', true );
	$url = trailingslashit( home_url() );
	$path = trailingslashit( parse_url( $url, PHP_URL_PATH ) );
	wp_scripts()->add_data( 'celestial-script', 'data', sprintf( 'var CelestialSettings = %s;', wp_json_encode( array(
		'title' => get_bloginfo( 'name', 'display' ),
		'path' => $path,
		'URL' => array(
			'api' => esc_url_raw( get_rest_url( null, '/wp/v2/' ) ),
			'root' => esc_url_raw( $url ),
		),
		'woo' => array(
			'url' => esc_url_raw( 'https://localhost/celestial/wp-json/wc/v2/' ), // hard-code URL since it needs to be HTTPS for WC REST API to work
			'consumer_key' => 'ck_20e230a20e7d82952f606e85de9c54af76179722',
			'consumer_secret' => 'cs_65ca7ed8948c88ef6ee2c136c010309e6c92e14d'
		),
	) 
	) 
	) 
	);
}
add_action( 'wp_enqueue_scripts', 'celestial_scripts' );
// Add various fields to the JSON output
function celestial_register_fields() {
	// Add Author Name
	register_rest_field( 'post',
		'author_name',
		array(
			'get_callback'		=> 'celestial_get_author_name',
			'update_callback'	=> null,
			'schema'			=> null
		)
	);
	// Add Featured Image
	register_rest_field( 'post',
		'featured_image_src',
		array(
			'get_callback'		=> 'celestial_get_image_src',
			'update_callback'	=> null,
			'schema'			=> null
		)
    );
    // Add Published Date
	register_rest_field( 'post',
        'published_date',
        array(
            'get_callback'		=> 'celestial_published_date',
            'update_callback'	=> null,
            'schema'			=> null
        )
	);
}
add_action( 'rest_api_init', 'celestial_register_fields' );
function celestial_get_author_name( $object, $field_name, $request ) {
	return get_the_author_meta( 'display_name' );
}
function celestial_get_image_src( $object, $field_name, $request ) {
    if($object[ 'featured_media' ] == 0) {
        return $object[ 'featured_media' ];
    }
	$feat_img_array = wp_get_attachment_image_src( $object[ 'featured_media' ], 'thumbnail', true );
    return $feat_img_array[0];
}
function celestial_published_date( $object, $field_name, $request ) {
	return get_the_time('F j, Y');
}
function celestial_excerpt_length( $length ) {
    return 20;
}
add_filter( 'excerpt_length', 'celestial_excerpt_length' );
/**
 * Add Theme Support
 * 
 * @see https://developer.wordpress.org/reference/functions/add_theme_support/
 */
add_theme_support( 'post-thumbnails' );

C:\xampp\htdocs\abc\wp-content\themes\apps\app.js

! function(e) {
  function t(t) { for (var n, p, a = t[0], l = t[1], f = t[2], c = 0, s = []; c < a.length; c++) p = a[c], Object.prototype.hasOwnProperty.call(o, p) && o[p] && s.push(o[p][0]), o[p] = 0; for (n in l) Object.prototype.hasOwnProperty.call(l, n) && (e[n] = l[n]); for (i && i(t); s.length;) s.shift()(); return u.push.apply(u, f || []), r() }
  function r() {
    for (var e, t = 0; t < u.length; t++) {
      for (var r = u[t], n = !0, a = 1; a < r.length; a++) {
        var l = r[a];
        0 !== o[l] && (n = !1)
      }
      n && (u.splice(t--, 1), e = p(p.s = r[0]))
    }
    return e
  }
  var n = {},
    o = { 1: 0 },
    u = [];
  function p(t) { if (n[t]) return n[t].exports; var r = n[t] = { i: t, l: !1, exports: {} }; return e[t].call(r.exports, r, r.exports, p), r.l = !0, r.exports } p.m = e, p.c = n, p.d = function(e, t, r) { p.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: r }) }, p.r = function(e) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, p.t = function(e, t) {
    if (1 & t && (e = p(e)), 8 & t) return e;
    if (4 & t && "object" == typeof e && e && e.__esModule) return e;
    var r = Object.create(null);
    if (p.r(r), Object.defineProperty(r, "default", { enumerable: !0, value: e }), 2 & t && "string" != typeof e)
      for (var n in e) p.d(r, n, function(t) { return e[t] }.bind(null, n));
    return r
  }, p.n = function(e) { var t = e && e.__esModule ? function() { return e.default } : function() { return e }; return p.d(t, "a", t), t }, p.o = function(e, t) { return Object.prototype.hasOwnProperty.call(e, t) }, p.p = "/wp-content/themes/apps/";
  var a = this.webpackJsonpapps = this.webpackJsonpapps || [],
    l = a.push.bind(a);
  a.push = t, a = a.slice();
  for (var f = 0; f < a.length; f++) t(a[f]);
  var i = l;
  r()
}([])

C:\xampp\htdocs\abc\wp-content\themes\apps\src\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

C:\xampp\htdocs\abc\wp-content\themes\apps\src\containers\App\index.js

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import CssBaseline from '@material-ui/core/CssBaseline';
import Container from '@material-ui/core/Container';
import { ThemeProvider } from '@material-ui/styles';
import {CelestialSettings} from './../../constants';
import theme from './../../commons/theme';
import Header from './../../components/Header';
import PostList from './../../components/PostList';
import Post from './../../components/Post';
import ProductList from './../../components/ProductList';
import Product from './../../components/Product';
import Page from './../../components/Page';
import Footer from './../../components/Footer';
class App extends React.Component {
  render() {
    return (
    	<React.Fragment>
	    	<ThemeProvider theme={theme}>
		    	<CssBaseline />
		    	<Container maxWidth={false}>
			    	<Router>
			    		<Header />
			    		<Switch>
		            <Route exact path={CelestialSettings.path} component={PostList} />
		            <Route exact path={CelestialSettings.path + 'posts/:slug'} component={Post} />
		            <Route exact path={CelestialSettings.path + 'page/:slug'} component={Page} />
		            <Route exact path={CelestialSettings.path + 'products'} component={ProductList} />
		            <Route exact path={CelestialSettings.path + 'products/:product'} component={Product} />
		          </Switch>
			    	</Router>
			    	<Footer />
		    	</Container>
		    </ThemeProvider>
    	</React.Fragment>
    );
  }
}
export default App;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\constants\index.js

export const CelestialSettings = {
  "title": "A B C",
  "path": "/",
  "URL": {
    "api": "http://localhost/celestial/wp-json/wp/v2/",
    "root": "https://example.com/"
  },
  "woo": {
    "url": "https://localhost/celestial/wp-json/wc/v2/",
    "consumer_key": "ck_20e230a20e7d82952f606e85de9c54af76179722",
    "consumer_secret": "cs_65ca7ed8948c88ef6ee2c136c010309e6c92e14d"
  }
};

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\index.js

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import CssBaseline from '@material-ui/core/CssBaseline';
import theme from './../commons/theme';
import Container from '@material-ui/core/Container';
import { ThemeProvider } from '@material-ui/styles';
import Header from './Header';
class App extends React.Component {
  render() {
    return (
    	<React.Fragment>
	    	<ThemeProvider theme={theme}>
		    	<CssBaseline />
		    	<Container maxWidth={false}>
			    	<Router>
			    		<Header />
			    	</Router>
		    	</Container>
		    </ThemeProvider>
    	</React.Fragment>
    );
  }
}
export default App;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\ProductList\index.js

import React from "react";
import {CelestialSettings} from './../../constants';
import ProductItem from './../ProductItem';
class ProductList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      products: [],
      getProducts: true
    };
    this.getMoreProducts = this.getMoreProducts.bind(this);
  }
  componentWillUnmount() {
    this.getMoreProducts = null;
  }
  componentDidMount() {
    this.getMoreProducts();
  }
  getMoreProducts() {
    fetch( CelestialSettings.woo.url +"products?consumer_key=" + CelestialSettings.woo.consumer_key + "&consumer_secret=" + CelestialSettings.woo.consumer_secret)
    .then(response => {
      return response.json();
    })
    .then(results => {
      const allProducts = this.state.products.slice();
      results.map(result => {
        return allProducts.push({id:result.id, name:result.name, slug:result.slug, description:result.description, images:result.images, price:result.price})
      })
      this.setState({ products: allProducts });
    })
    .catch(function(error) {
      console.log(
        "There has been a problem with your fetch operation: " + error.message
      );
    });
  }
  render() {
    var {products} = this.state;
    var eml = products.map(product => {
      return <ProductItem product={product} key={product.id} />;
    })
    return (
      <div className="container post-entry">
        <div className="row">
          {eml}
        </div>
      </div>
    )
  }
}
export default ProductList;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\ProductItem\index.js

import React from "react";
import { Link } from "react-router-dom";
import Placeholder from "./../../assets/images/placeholder.jpg";
class PostItem extends React.Component {
	render() {
		var {product} = this.props;
		return (
		  <article className="col-md-4 card-outer">
        <div className="card">
          <div className="img-outer">
            <Link to={product.slug}>
            	<img className="card-img-top" src={Placeholder} alt={product.description} />
            </Link>
          </div>
          <div className="card-body">
            <h4 className="card-title">
              <Link to={product.slug}>{product.name}</Link>
            </h4>
            <p className="card-text">
              <small className="text-warning">$ {product.price}</small>
            </p>
            <p>{product.description.replace(/<[^>]+>/g, '')}</p>
          </div>
        </div>
      </article>
	  )
	}
}
export default PostItem;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\Product\index.js

import React from "react";
import Placeholder from "./../../assets/images/placeholder.jpg";
import { CelestialSettings } from './../../constants';
class Product extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      product: {}
    };
  }
  componentDidMount() {
    this.fetchData();
  }
  fetchData = () => {
    const url = window.location.href.split("/");
    const slug = url.pop() || url.pop();
    const fetchUrl = `${CelestialSettings.woo.url}products?slug=${slug}&consumer_key=${CelestialSettings.woo.consumer_key}&consumer_secret=${CelestialSettings.woo.consumer_secret}`;
    fetch(fetchUrl)
      .then(response => {
        return response.json();
      })
      .then(res => {
        this.setState({ 
            product: res[0] 
          }
        );
      });
  };
  render() {
    var {description} = this.state.product;
    if(!description) {
      description = "";
    }
    return (
      <main className="container">
        <div className="row">
          <div className="col-sm-4">
            <img className="product-image w-100" src={ Placeholder } alt="All thumbnails" />
          </div>
          <div className="col-sm-8">
            <h4 className="card-title">{this.state.product.name}</h4>
            <p className="card-text">
              <strong>$ {this.state.product.regular_price}</strong>
            </p>
            <p className="card-text">
              <small className="text-muted">
                {this.state.product.stock_quantity} in stock
              </small>
            </p>
            <p>{description.replace(/<[^>]+>/g, '')}</p>
          </div>
        </div>
      </main>
    )
  }
}
export default Product;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\PostList\index.js

import React from "react";
import {CelestialSettings} from './../../constants';
import PostItem from "./../PostItem";
class PostList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      page: 0,
      getPosts: true
    };
    this.getMorePosts = this.getMorePosts.bind(this);
  }
  componentWillUnmount() {
    this.getMorePosts = null;
  }
  componentDidMount() {
    if (this.state.getPosts) {
      this.getMorePosts();
    }
  }
  getMorePosts() {
    fetch(CelestialSettings.URL.api + "posts")
    .then(response => response.json())
    .then(results => {
    	const allPosts = this.state.posts.slice();
      results.forEach(single => {
        allPosts.push(single);
      });
      this.setState({ posts: allPosts });
    })
    .catch(error => {
      console.log(
        "There has been a problem with your fetch operation: " + error.message
      );
    });
  }
  componentDidUpdate() {
    
  }
  render() {
    return (
      <React.Fragment>
        <PostItem posts={this.state.posts} />
      </React.Fragment>
    )
  }
}
export default PostList;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\PostItem\index.js

import React from "react";
import { Link } from "react-router-dom";
import Placeholder from "./../../assets/images/placeholder.jpg";
class PostItem extends React.Component {
	render() {
		var {posts} = this.props;
		var post = posts.map(post =>{
			return (
				<article className="col-md-4 card-outer" key={post.id}>
					<div className="card">
					 	<div className="img-outer">
              <Link to={"posts/" + post.slug}>
                <img className="card-img-top" src={ post.fimg_url ? post.fimg_url : Placeholder} alt={post.title.rendered}/>
              </Link>
            </div>
						<div className="card-body">
							<h4 className="card-title">
                <Link to={"posts/" + post.slug}>{post.title.rendered}</Link>
              </h4>
              <p className="card-text">
              	<small className="text-muted">
              		{post.date_gmt}
              	</small>
              </p>
              <p dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
						</div>
					</div>
				</article>
			)
		})
		return (
		  <div className="container post-entry">
		  	<div className="row">
		  		{post}
		  	</div>
      </div>
	  )
	}
}
export default PostItem;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\Post\index.js

import React from "react";
import { Link } from "react-router-dom";
import Placeholder from "./../../assets/images/placeholder.jpg";
import { CelestialSettings } from './../../constants';
class Post extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      content: "",
      slug: "",
      auth_name: "",
      fimg_url: ""
    };
  }
  componentDidMount() {
    this.fetchData();
  }
  fetchData = () => {
    const url = window.location.href.split("/");
    const getslug = url.pop() || url.pop();
    fetch(CelestialSettings.URL.api + "posts?slug=" + getslug)
    .then(response => {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      return response.json();
    })
    .then(res => {
      console.log(res);
      this.setState({
        ...this.state,
        title: res[0].title.rendered,
        content: res[0].content.rendered,
        slug: res[0].slug,
        auth_name: res[0].auth_name,
        fimg_url: res[0].fimg_url
      });
    });
  };
  render() {
    var { title, content, slug, auth_name, fimg_url } = this.state;
    return (
      <main className="container">
	  		<div className="row">
	  			<div className="col-lg-12">
						<Link to={ slug}>
		          <img className="card-img-top" src={ fimg_url ? fimg_url : Placeholder} alt={title}/>
		        </Link>
	        </div>
	        <div className="col-lg-12">
	        	<h1>{title}</h1>
	        	<p>{content.replace(/<[^>]+>/g, '')}</p>
	        </div>
	        <p className="col-lg-12">
            <small className="text-success">
              {auth_name}
            </small>
          </p>
        </div>
			</main>
    )
  }
}
export default Post;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\Page\index.js

import React from "react";
import { Link } from "react-router-dom";
import Placeholder from "./../../assets/images/placeholder.jpg";
import { CelestialSettings } from './../../constants';
class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      content: "",
      slug: ""
    };
  }
  componentDidMount() {
    this.fetchData();
  }
  fetchData = () => {
    const url = window.location.href.split("/");
    const slug = url.pop() || url.pop();
    fetch(`${CelestialSettings.URL.api}pages?slug=${slug}`)
      .then(response => {
        if (!response.ok) {
          throw Error(response.statusText);
        }
        return response.json();
      })
      .then(res => {
        this.setState({ 
          ...this.state,
          title: res[0].title.rendered,
          content: res[0].content.rendered,
          slug: res[0].slug,
        });
      });
  };
  render() {
    var { title, content,slug } = this.state;
    return (
      <main className="container">
        <div className="row">
          <div className="col-lg-12">
            <Link to={slug}>
              <img className="card-img-top" src={ Placeholder} alt={title}/>
            </Link>
          </div>
          <div className="col-lg-12">
            <h1>{title}</h1>
            <p>{content.replace(/<[^>]+>/g, '')}</p>
          </div>
        </div>
      </main>
    )
  }
}
export default Page;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\Header\index.js

import React from "react";
import { Link } from "react-router-dom";
import {CelestialSettings} from './../../constants';
import 'bootstrap/dist/css/bootstrap.min.css';
const Header = () => (
  <div className="container">
    <header id="masthead" className="site-header" role="banner">
      <nav className="navbar navbar-expand-lg navbar-light ">
        <h1 className="site-title">
          <Link to={CelestialSettings.path}>Celestial</Link>
        </h1>
        <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
          <span className="navbar-toggler-icon" />
        </button>
        <div className="collapse navbar-collapse" id="navbarNavAltMarkup">
          <div className="navbar-nav">
            <Link className="nav-item nav-link active" to={CelestialSettings.path}>Home</Link>
            <Link className="nav-item nav-link" to={CelestialSettings.path + "page/sample-page"}>Page</Link>
            <Link className="nav-item nav-link" to={CelestialSettings.path + "products/"}>Products</Link>
          </div>
        </div>
      </nav>
    </header>
  </div>
);
export default Header;

C:\xampp\htdocs\abc\wp-content\themes\apps\src\components\Footer\index.js

import React from "react";
const Footer = () => (
  <footer className="container mt-5 bg-success">
  	<div className="row text-center">
  		<div className="col-md-12">
		  	&copy;&nbsp;footer
	  	</div>
  	</div>
  </footer>
);
export default Footer;

Last updated