Mémos

M é m o - l a b .

WordPress et React

WordPress propose l’avantage de livrer une zone d’administration complète et installable en quelques secondes. dans la majorité des cas, la partie front end est dévelppée en php selon les « règles WordPress ». Il est pourtant possible de n’utiliser WordPress que pour bénéficier de sa zone d’administration et de confier l’affichage à une autre application. Dans notre cas, l’affichage sera réalisé en javascript en utilisant le framework front-end React. Cela est réalisable grâce à l’API REST WordPress. Pour rappel une api (Application Programming Interfaces) est une interface qui permet à des applications de s’échanger des informations, des services ou des données.
Pour résumer, tous les contenus sont saisis depuis l’administration WordPress et sont affichés en React grâce à l’API REST WordPress.

Après avoir installé WordPress (chapitre Installer WordPress), se positionner dans une console (cmder, invite de commande…) sur le dossier themes (<projet>/wp-content/themes) et saisir la commande create-react-app react-app. Un projet React clé en main est alors disponible sous le dossier react-app (<projet>/wp-content/themes/react-app). Un router étant également nécessaire, nous installons cette dépendance via npm (gestionnaire de paquets de nodejs) en saisissant en console sur le dossier react-app, la commande npm install react-router-dom --save. Pour admirer notre site web, lancer en console, sur le dossier react-app, le serveur intégré en saisissant la commande yarn start. Un onglet du navigateur s’ouvre automatiquement sous l’url http://localhost/3000.

L’API REST WordPress est disponible depuis l’url http://localhost/<projet>/wp-json/wp/v2. Elle liste les url qui pourront être utilisées par l’application front-end (React) pour récupérer les données entrées en back-office. Comme pour la plupart des APIs, les données transmises par l’API WordPress sont au format JSON. React reçoit donc des données au format json.

Dans notre projet react-app, nous modifions le fichier App.js qui est le point d’entrée de notre site et nous y intégrons le routage. Homepage.js affiche la page d’accueil, Articles.js affiche une liste des articles et le click sur le titre affiche l’article dans la page Article.js. Les 3 fichiers Homepage.js, Articles.js et Article.js sont placés dans le dossier react-app/src/component. Le fichier de style app.css est placé dans le dossier react-app/src/dist/css/app.css.
Rqe : Si l’on souhaite ajouter des classes dans les balises html des fichiers js, il faut utiliser l’attribut className et non class.

La compréhension par le code





App.js

import React, {Component} from 'react';
// Intégration du router
import {Link, Route, Switch, BrowserRouter} from 'react-router-dom';

// Importation des fichiers Homepage.js, Articles.js et Article.js
import Homepage from './component/Homepage';
import Articles from './component/Articles';
import Article from './component/Article';

// Importation du fichier app.css
import './dist/css/app.css';

class App extends Component {
    render () {
        return (
            // Menu de navigation
            <BrowserRouter>
                <nav>
                    <ul className="">
                        <li className="">
                            <Link className="" to="/">Accueil</Link>
                        </li>
                        <li className="">
                            <Link className="" to="/articles">Articles</Link>
                        </li>
                    </ul>
                </nav>

                // Système de routing
                <Switch>
                    <Route path="/" exact component={Homepage} />
                    <Route path="/articles" component={Articles} />
                    // id de l'article que l'on récupère dans
                    // le fichier Article.js
                    <Route path="/article/:id" component={Article} />
                </Switch>
            </BrowserRouter>
        )
    }
}
export default App;



Homepage.js

import React, {Component} from 'react';

class Homepage extends Component 
{
    render() {
        return (
            <div>
                <p>Lorem ipsum dolor sit amet.</p>
            </div>
        )
    }
}
export default Homepage;



Articles.js

import React, { Component } from 'react';
import { Link } from 'react-router-dom';

class Articles extends Component 
{
    constructor() {
        // super() permet d'appeler le constructeur de la classe
        // parente (React.Component) afin de pouvoir utiliser this
        super()
        this.state = {
            articles: null,
            // loading est un booléen représantant l'état de 
            // chargement des articles   
            loading: true          
        }
        this.getArticles()
    }

    getArticles() {
        // Récupération des articles via l'API REST WordPress
        let url = 'http://localhost/wp_tryreact/wp-json/wp/v2/posts'
        fetch(url)
        .then(response => response.json())
        .then((response) => {
            this.setState({
                articles: response,
                loading: false
            })
        })
    }

    displayArticles() {
        // map produit une liste
        return this.state.articles.map((article, key) => {
            return(
                // Chaque élément d'une liste doit posséder une clé unique
                <div key={key}>
                    <h3>
                        // article.id retourne l'id de l'article
                        <Link className="" to={'/article/' +article.id} dangerouslySetInnerHTML={{__html: article.title.rendered}}></Link>
                    </h3>
                    <p dangerouslySetInnerHTML={{__html: article.content.rendered}}></p>      
                </div>
            )
        })
    }

    render() {
        return (
            <div>
                <h3>Liste des articles</h3>
                // Affichage de la liste si la variable loading est à true
                {this.state.loading ? <div>Loading...</div> : this.displayArticles()}
            </div>
        )
    }
}
export default Articles;



Article.js

import React, { Component } from 'react';

class Article extends Component 
{
    constructor(props) {
        // super() permet d'appeler le constructeur de la classe
        // parente (React.Component) afin de pouvoir utiliser this
        super(props)
        // id de l'article passé en paramètre de la route (cf App.js)
        this.id = props.match.params.id
        this.state = {
        	article: null,
            loading: true
        }
        this.getArticle()
    }

    getArticle() {
        // Récupération de l'article par son id via l'API REST WordPress
        let url = 'http://localhost/wp_tryreact/wp-json/wp/v2/posts/' +this.id
        fetch(url)
        .then(response => response.json())
        .then((response) => {
            this.setState({
                article: response,
                loading: false
            })
        })
    }

    displayArticle() {
        return(
            <div>
                <h3 dangerouslySetInnerHTML={{__html: this.state.article.title.rendered}}></h3>
                <p dangerouslySetInnerHTML={{__html: this.state.article.content.rendered}}></p>      
            </div>
        )
    }

    render() {
        return (
            <div>
                // Affichage de l'article si la variable loading est à true
                {this.state.loading ? <div>Loading...</div> : this.displayArticle()}
            </div>
        )
    }
}
export default Article;

Remarque concernant l’API REST de WordPress. Il est possible de créer sa propre api en utilisant l’action rest_api-init que l’on peut placer dans le fichier functions.php. Il faut alors définir L’url appelée depuis React, personnaliser la requête WordPress et retourner les résultats au format json.





functions.php

<?php 
// API personnalisée récupérant les articles
function api_posts() {
    // Requête WordPresss récupérant les articles
    $posts = get_posts([
        'post_type' => 'post',
        'posts_per_page'=> -1, 
        'numberposts'=> -1
    ]);
    // Résultat retourné au format json
    return $posts;
}

// API personnalisée récupérant un article par son id
function api_post($data) {
    // Requête WordPresss récupérant un article
    $post = get_post($data['id']);
    // Résultat retourné au format json
    return $post;
}

add_action('rest_api_init', function () {
    register_rest_route('api', '/posts', [
        'methods' => 'GET',
        'callback' => 'api_posts'
    ]);
    register_rest_route('api', '/post/(?P<id>[\d]+)', [
        'methods' => 'GET',
        'callback' => 'api_post',
        'args' => [
            'id'
        ]
    ]);
});



Articles.js
// la ligne d'appel de l'url devient :
let url = 'http://localhost/wp_tryreact/wp-json/api/posts'

// la fonction displayArticles() devient :
 displayArticles() {
    return this.state.articles.map((article) => {
        return(
            <div>
                <h3>
                    <Link className="" to={'/article/' +article.ID} dangerouslySetInnerHTML={{ __html: article.post_title}}></Link>
                </h3>
                <p dangerouslySetInnerHTML={{__html: article.post_content}}></p>      
            </div>
        )
    })
}



Article.js
// la ligne d'appel de l'url devient :
let url = 'http://localhost/wp_tryreact/wp-json/api/post/' +this.id

// la fonction displayArticle() devient :
displayArticle() {
    return(
        <div>
            <h3 dangerouslySetInnerHTML={{__html: this.state.article.post_title}}></h3>
            <p dangerouslySetInnerHTML={{__html: this.state.article.post_content}}></p>      
        </div>
    )
}