Skip to Content
WordPressPartie 8 — Gutenberg : blocs custom en ReactTutoriel : le bloc « Livre à la une »

Chapitre 8.9 — Tutoriel : le bloc « Livre à la une »

⏱️ TL;DR — On assemble la Partie 8 en un bloc réel, branché sur le plugin wpr-books (Partie 7). Le bloc « Livre à la une » est dynamique (rendu PHP) : dans l’éditeur, l’utilisateur choisit un livre (via useSelect + getEntityRecords) et règle l’affichage (Inspector) ; sur le front, un render.php affiche titre, image, prix (meta wpr_prix). Il combine block.json, edit/attributs, contrôles, data store et rendu dynamique — tout le chapitre en une pièce.

🎯 Objectifs

  • Construire un bloc dynamique de bout en bout.
  • Charger et sélectionner une entité (livre) via @wordpress/data.
  • Rendre le front en PHP, sécurisé, avec les supports natifs.
  • Comprendre le pont Partie 7 → 8 (et vers 9).

1. block.json

{ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "wpr/livre-une", "title": "Livre à la une", "category": "widgets", "icon": "book", "textdomain": "wpr-books", "attributes": { "livreId": { "type": "number" }, "afficherPrix":{ "type": "boolean", "default": true } }, "supports": { "html": false, "align": ["wide"], "color": { "background": true, "text": true }, "spacing": { "padding": true } }, "editorScript": "file:./index.js", "editorStyle": "file:./index.css", "style": "file:./style-index.css", "render": "file:./render.php" }

Bloc dynamique (render), avec un attribut livreId (le livre choisi) et afficherPrix. Les supports offrent couleur/alignement/espacement gratuitement.


2. edit.js : sélection du livre + réglages

import { useBlockProps, InspectorControls } from '@wordpress/block-editor'; import { PanelBody, ComboboxControl, ToggleControl, Spinner, Placeholder } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; export default function Edit( { attributes, setAttributes } ) { const { livreId, afficherPrix } = attributes; const blockProps = useBlockProps(); // Charger la liste des livres (résolution auto → REST /wp/v2/livre) const { livres, chargement } = useSelect( ( select ) => { const query = { per_page: 50, _fields: 'id,title' }; return { livres: select( coreStore ).getEntityRecords( 'postType', 'livre', query ), chargement: select( coreStore ).isResolving( 'getEntityRecords', [ 'postType', 'livre', query ] ), }; }, [] ); // Charger le livre sélectionné (titre + meta) const livre = useSelect( ( select ) => livreId ? select( coreStore ).getEntityRecord( 'postType', 'livre', livreId ) : null, [ livreId ] ); const options = ( livres || [] ).map( ( l ) => ( { value: l.id, label: l.title.rendered || `#${ l.id }`, } ) ); return ( <> <InspectorControls> <PanelBody title={ __( 'Réglages', 'wpr-books' ) }> <ComboboxControl label={ __( 'Livre', 'wpr-books' ) } value={ livreId } options={ options } onChange={ ( id ) => setAttributes( { livreId: id ? Number( id ) : undefined } ) } /> <ToggleControl label={ __( 'Afficher le prix', 'wpr-books' ) } checked={ afficherPrix } onChange={ ( v ) => setAttributes( { afficherPrix: v } ) } /> </PanelBody> </InspectorControls> <div { ...blockProps }> { chargement && ! livres && <Spinner /> } { ! livreId && ( <Placeholder icon="book" label={ __( 'Livre à la une', 'wpr-books' ) } instructions={ __( 'Choisissez un livre dans les réglages.', 'wpr-books' ) } /> ) } { livreId && ! livre && <Spinner /> } { livre && ( <div className="wpr-livre-une__apercu"> <strong>{ livre.title?.rendered }</strong> { afficherPrix && livre.meta?.wpr_prix ? <span> — { livre.meta.wpr_prix } €</span> : null } </div> ) } </div> </> ); }

Points clés :

  • getEntityRecords('postType','livre') charge la liste (le store fait le fetch REST — d’où l’importance de show_in_rest sur le CPT, ch. 7.3).
  • ComboboxControl = un select recherchable pour choisir le livre.
  • Gestion des états (chargement Spinner, vide Placeholder, prêt) — vos réflexes React.
  • livre.meta?.wpr_prix : la meta est là parce qu’ elle est enregistrée avec show_in_rest (ch. 7.4).

⚠️ Piège — la meta absente dans l’entité. Si livre.meta est vide/undefined, vérifiez que wpr_prix est bien register_post_meta(..., show_in_rest => true) (Partie 7) : sans ça, la REST (et donc le store) ne renvoie pas la meta.


3. save.js : rien (dynamique)

export default function save() { return null; // rendu par render.php }

4. render.php : le front, sécurisé

<?php // $attributes, $content, $block disponibles $livre_id = isset( $attributes['livreId'] ) ? absint( $attributes['livreId'] ) : 0; if ( ! $livre_id || get_post_type( $livre_id ) !== 'livre' ) { return; // rien à afficher } $titre = get_the_title( $livre_id ); $url = get_permalink( $livre_id ); $prix = get_post_meta( $livre_id, 'wpr_prix', true ); $img = get_the_post_thumbnail( $livre_id, 'medium' ); $wrapper = get_block_wrapper_attributes( array( 'class' => 'wpr-livre-une' ) ); ?> <div <?php echo $wrapper; ?>> <a href="<?php echo esc_url( $url ); ?>" class="wpr-livre-une__lien"> <?php echo $img; // HTML d'image généré par le cœur (déjà sûr) ?> <span class="wpr-livre-une__titre"><?php echo esc_html( $titre ); ?></span> </a> <?php if ( ! empty( $attributes['afficherPrix'] ) && $prix ) : ?> <span class="wpr-livre-une__prix"> <?php echo esc_html( number_format_i18n( (float) $prix, 2 ) ); ?> </span> <?php endif; ?> </div>
  • get_block_wrapper_attributes() applique les supports (couleur, alignement, padding) sur le front (ch. 8.5).
  • Échappement partout (esc_url, esc_html) — sécurité (ch. 7.7).
  • Le prix vient de la même meta que la metabox de la Partie 7 : une seule source de vérité.

5. Enregistrer le bloc depuis wpr-books

Dans le plugin wpr-books (Partie 7), on ajoute :

add_action( 'init', function () { register_block_type( WPR_BOOKS_PATH . 'build/livre-une' ); // dossier de build du bloc } );

Le bloc vit dans le plugin wpr-books : le CPT, la meta et le bloc forment un tout cohérent.


6. Ce qu’on a démontré (et la suite)

  • Un bloc dynamique complet : sélection d’entité en React, rendu serveur sécurisé.
  • Le data store relie le bloc aux données du CMS sans REST manuel.
  • La cohérence Partie 7 (données) → Partie 8 (bloc) : la même meta alimente admin, bloc et — bientôt — le front headless (Partie 9).

💡 Pour un dev React — Vous venez de faire, dans WordPress, ce que vous faites tous les jours : un composant qui charge une liste depuis une API (getEntityRecords), laisse choisir un élément (Combobox), gère les états (loading/empty/ready), et affiche des données — avec un rendu serveur pour le front. Le store core vous a épargné tout le câblage API. C’est exactement le genre de compétence (React dans WordPress) qui est rare et bien payée (Partie 13).


✏️ Exercices

  1. Pourquoi ce bloc est-il dynamique plutôt que statique ?
  2. Qu’est-ce qui garantit que getEntityRecords('postType','livre') fonctionne (côté Partie 7) ?
  3. La liste des livres se charge, mais le prix ne s’affiche jamais dans l’aperçu. Quelle cause vérifiez-vous ?

✅ Solution

  1. Parce que le contenu affiché dépend d’une donnée qui évolue (le livre, son titre, son prix). Un save statique figerait ces valeurs à l’insertion et provoquerait une invalidation si le livre change ; le rendu PHP recalcule à chaque affichage, toujours à jour.
  2. Le CPT livre est enregistré avec show_in_rest => true (Partie 7, ch. 7.3) : la ressource /wp/v2/livre existe, et le store core peut la charger via getEntityRecords.
  3. La meta wpr_prix n’est probablement pas exposée : vérifier qu’elle est register_post_meta(..., 'show_in_rest' => true) (ch. 7.4). Sans ça, livre.meta.wpr_prix est absent du store.

🧠 Quiz de révision

1. Comment le bloc charge-t-il la liste des livres ?

Via useSelect + getEntityRecords('postType','livre') (le store fait le fetch REST).

2. Que renvoie save() pour ce bloc ?

null : le rendu front est produit par render.php (bloc dynamique).

3. Quel appel applique les supports sur le front ?

get_block_wrapper_attributes() dans render.php.

4. D’où vient le prix affiché ?

De la meta wpr_prix du livre (la même que la metabox de la Partie 7) : une seule source de vérité.

5. Où le bloc est-il enregistré ?

Dans le plugin wpr-books (register_block_type du dossier de build), avec le CPT et la meta.


Partie suivante : Partie 9 — REST API & WordPress headless (Next.js).