Artículo original escrito por Bret Cameron
Artículo original  How to Create a Modern Web App Using WordPress and React
Traducido y adaptado por Israel Palma

¿Quieres las ventajas de una moderna SPA React, pero un back-end que se sienta familiar? En este artículo, cubriremos cómo configurar el REST API de WordPress, incluyendo los "custom post types" (tipos de post personalizados) y "custom fields" (campos personalizados) así como también te mostramos como traer estos datos desde React.

Recientemente, he trabajado en un app React para un cliente, y ellos me hicieron esta pregunta: ¿Puedo usar React con WordPress?

Desde finales del 2015 la respuesta a esta pregunta ha sido, si. Pero los pasos necesarios para crear un sitio des-acoplado que trabaje bien podría no parecer tan clara o sencilla, especialmente para quienes no están familiarizados con WordPress y React.

En mi trayectoria al crear una aplicación (WordPress + React) que funcionara me encontré con un montón de obstáculos engañosos que en este artículo explicaré como evitar. También comparto muchos otros trucos y tips que aprendí en el camino.

Contenido

Parte 1: Antecedentes  

  • ¿Qué es un Headless CMS?
  • ¿Que necesito saber?
  • Acrónimos clave
  • ¿Dónde puedo ver los datos JSON de WordPress?

Parte 2: WordPress

  • Agregando un Custom Post Type
  • Cambiando el texto de relleno del título
  • Agregando un "Custom Field" a tu "Custom Post Type"
  • Haciendo  que los Custom Fields estén disponibles en formato JSON
  • Restringiendo la visibilidad de los datos JSON

Parte 3: React

  • Promesas en JavaScript
  • El método Fetch
  • Usando Promesas

Un ejemplo que trabaja bien

Conclusión

Parte 1: Antecedentes

1*V5SV3AAoVrt9qjiWsTabbg

¿Qué es un Headless CMS?

Antes usar un CMS como WordPress significaba que tenías que crear tu frontend usando PHP.

Ahora con headless CMS puedes construir tu front-end con cualquier tecnología que prefieras; esto gracias a la separación (des-acoplamiento) entre el front-end y el back-end vía una API. Si quieres crear una SPA (Aplicación de Página Única) usando React, Angular o Vue, y controlar el contenido usando un CMS como WordPress, ya es posible!

¿Qué necesito saber?

Sacarás el máximo provecho de este artículo si tienes:

  • Algún conocimiento de cómo funciona un CMS como WordPress, un poco de PHP, y alguna idea de como configurar un proyecto WordPress en tu computadora.
  • Entender JavaScript, incluyendo las características de las versiones ES6+ y la sintaxis de clases en React.

Acrónimos clave

La programación está llena de jerga, pero esta nos permite platicar de manera más rápida los conceptos en este artículo. Aquí un rápido repaso de los términos que usaremos:

  • CMS — Sistema para el Manejo de Contenido (content management system). Piensa en WordPress, Drupal, Joomla, Magento.
  • SPA — Aplicación de Página Única (Single-Page Application).  En lugar de re-cargar cada página por completo, una SPA carga el contenido dinámicamente. El código fundamental del sitio web (HTML, CSS y JavaScript) se carga una sóla vez. Piensa en React, Vue, Angular.
  • API — Interfaz de Programación de Aplicaciones (application programming interface). En términos sencillos, es una serie de definiciones que un servicio provee para permitirte tomar y utilizar sus datos. Google Maps tiene una, Medium tiene una. Y ahora cada website hecho con WordPress viene con una API incluida.
  • REST — Transferencia de Estado Representacional (representational state transfer). Un estilo de arquitectura web basado alrededor de los métodos HTTP: GET , PUT , POST y DELETE . El API de WordPress es un API REST o API “RESTful”.
  • HTTP — Protocolo de Transferencia de Hipertexto (hypertext transfer protocol). El conjunto de reglas utilizadas para transferir datos a través de la web. Se especifica el inicia de URLs como http or https (la versión segura).
  • JSON — Notación de Objetos JavaScript (JavaScript object notation). Aunque es un acrónimo derivado de JavaScript, JSON es un formato independiente de lenguajes para almacenar y transmitir datos.

En este artículo estamos usando WordPress como nuestro CMS. Lo que quiere decir que programaremos nuestro "backend" en PHP y usando la REST API de WordPress para enviar datos JSON al "frontend".

¿Dónde puedo ver los datos JSON de WordPress?

Antes de llegar a lo bueno, veamos una rápida nota acerca de en dónde puedes encontrar los datos JSON de tu sitio WordPress. En la actualidad , cada sitio WordPress cuenta con datos JSON a tu disposición (a menos que el propietario del sitio haya deshabilitado o restringido el acceso a estos). Puedes echar un vistazo al JSON principal de un sitio WordPress al agregar  /wp-json a la url del dominio principal del sitio.

Entonces, por ejemplo, puedes dar un vistazo a el JSON de WordPress.org visitando https://wordpress.org/wp-json. O, si estás corriendo un sitio WordPress de manera local, puedes ver sus datos JSON en la siguiente dirección  localhost/yoursitename/wp-json.

Para tener acceso a los datos de tus publicaciones (posts), escribe  localhost/yoursitename/wp-json/wp/v2/posts . Para un formato personalizado (custom post format), intercambia el nuevo formato (ej. movies) en lugar de  posts. Lo que ahora se ve como un bloque de texto ilegible nos permitirá usar WordPress como un "headless CMS"! - del inglés Sistema de Manejo de Contenidos sin Cabecera; lo que significa que no incluye la capa de presentación.

Parte 2: WordPress

1*jUVbqOjhcy-68oCBkIlm2Q

Para configurar tu API REST, la mayor parte de lo que necesitas sucederá en tu archivo functions.php. Asumiré que ya sabes como configurar un proyecto WordPress y tener acceso a este mediante  localhost.

Para la mayoría de los proyectos vas a querer utilizar un "custom post type", así que comencemos por configurar uno.

Agregando un Custom Post Type

Digamos que nuestro sitio es acerca de películas y que queremos un "post type" llamado 'movies'. Primero tendremos que asegurarnos que nuestro "post type" 'movies' (películas) se cargue lo antes posible, así que lo adjuntáremos al hook  init usando add_action :

add_action( 'init', 'movies_post_type' );

Estoy utilizando el nombre de función movies_post_type() , pero puedes llamar la función como tu gustes.

A continuación vamos a registrar 'movies' como un "post type" con la función register_post_type().

El siguiente bloque de código podría parecer demasiado complejo pero es relativamente simple: nuestra función toma muchos argumentos para controlar la funcionalidad de nuestro nuevo "post type" y la mayoría de estos se explican muy bien por sus propios nombres. Pondremos estos argumentos en nuestro arreglo  (array) $args .

Uno de los argumentos es  labels, este también puede tomar diferentes argumentos así que lo separaremos en un arreglo aparte, $labels , dándonos:

Dos de los argumentos más importantes son 'supports' y 'taxomonies' , lo cual se debe a que estos argumentos controlan cuales de los campos post nativos serán accesibles en nuestro nuevo "post type".

En el código de arriba optamos por sólo tres  'supports':

  • 'title'— el título de cada post.
  • 'editor'— el editor de texto principal, que usaremos en nuestra descripción.
  • 'thumbnail'— la imagen destacada del post.

Para ver la lista completa de lo que se encuentra disponible revisa aquí para supports y acá para taxonomías.

"Generate WordPress" también tiene una práctica herramienta para ayudarte a codificar los "custom post types" mucho más

Cambiando el texto de relleno (placeholder) del título.

Si el texto de relleno "escribe el título aquí" (en inglés "enter title here") es un poco confuso para tu "custom post type" puedes editarlo en una función distinta.

Agrega un campo personalizado a tu custom post type

¿Que tal si necesitas un campo que no viene pre-definido en la interface de WordPress? Por ejemplo, digamos que queremos un campo especial llamado "Género". En ese caso, necesitarás usar la función add_meta_boxes() .

Esto debido a que necesitaremos adjuntar una nueva función al hook (gancho) de WordPress  add_meta_boxes:

add_action( 'add_meta_boxes', 'genre_meta_box' );

Dentro de nuestra nueva función, necesitamos llamar la función de WordPress  add_meta_box() de la siguiente manera:

function genre_meta_box() {  add_meta_box(    'global-notice',    __( 'Genre', 'sitepoint' ),    'genre_meta_box_callback',    'movies',    'side',    'low'  );}

Puedes leer más acerca de los argumentos de esta función aquí. Para nuestros propósitos la parte más crítica es la función callback, que hemos llamado  genre_meta_box_callback . Esta define los contenidos del meta box. Sólo necesitamos ingresar texto así que podemos usar:

function genre_meta_box_callback() {  global $post;  $custom = get_post_custom($post->ID);  $genre = $custom["genre"][0];  ?>  <input style="width:100%" name="genre" value="<?php   echo $genre; ?>" />  <?php};

Finalmente, nuestro custom field no guardará el valor que contiene a menos que le digamos que lo haga. Para este propósito podemos definir una nueva función  save_genre() y adjuntar a el hook  save_post de WordPress:

function save_genre(){  global $post;  update_post_meta($post->ID, "printer_category",   $_POST["printer_category"]);};
add_action( 'save_post', 'save_genre' );

Ya completo, el código que usamos para crear el "custom field" debería verse algo así:

Haciendo que los custom fields estén disponibles en formato JSON

Nuestros "custom posts" están automáticamente disponibles en formato JSON. Para nuestro post type "movies", los datos en formato JSON están disponibles en  localhost/yoursitename/wp-json/wp/v2/movies .

Sin embargo nuestros custom fields no son están incluídos automáticamente, así que necesitamos agregar una función para asegurarnos de que también son accesibles mediante la REST API.  

Primero, necesitaremos adjuntar una nueva función al hook  rest_api_init :

add_action( 'rest_api_init', 'register_genre_as_rest_field' );

Luego, podremos utilizar la función register_rest_field() así:

function register_genre_as_rest_field() {  
	register_rest_field(    
    	'movies',    
        'genre',    
        array(      
        'get_callback' => 'get_genre_meta_field',      
        'update_callback' => null,      
        'schema' => null,
        ) 
     );
 }

Esta función toma un arreglo con los callbacks get y update . Para un caso de uso más simple y claro, podemos sólo especificar un  'get_callback' :

function get_genre_meta_field( $object, $field_name, $value ) {  
	return get_post_meta($object['id'])[$field_name][0];
}

En términos generales, este es el código necesario para registrar un custom field.

Haciendo que las URLs de las imágenes destacadas estén disponibles en formato JSON

La REST API de WordPress no incluye de antemano una URL para tus imágenes destacadas. Para hacerlo más fácil tener acceso puedes usar el siguiente código:

El filtro de WordPress rest_prepare_posts es dinámico, así que podemos meter nuestro custom post type en el lugar de la palabra "posts", de la siguiente forma:  rest_prepare_movies .

Restringiendo cuáles datos JSON son visibles

Estamos casi listos para empezar a traer datos a nuestro app React, pero hay una optimización más que podemos hacer, al limitar los datos que están disponibles.

Algunos datos vienen de manera estándar pero que podrías nunca necesitar en tu frontend y (de ser este el caso) podemos removerlos usando un filtro, como este. Puedes encontrar los nombres  de los tipos de datos dándole un vistazo a la URL  /wp-json/wp/v2/movies  de tu website.

Con eso terminado, una vez que hayas agregado unas cuántas películas usando el backend de WordPress, tenemos todo lo que necesitamos para empezar a traer los datos a React!

Parte 3: React

1*qKj1pMwgVFGtOID_wD1N-g

Para obtener datos externos en JavaScript, necesitas usar promesas. Esto probablemente tendrá implicaciones para la manera en que quieres estructurar tus componentes React y en mi caso (convertir un proyecto React existente), tuve que re-escribir una cantidad considerable de código.  

Promesas en JavaScript

Las promesas en JavaScript son utilizadas para manejar acciones asíncronas; cosas que suceden fuera del orden de ejecución usual o síncrono.

La buena noticia es que JavaScript asíncrono es más fácil de lo que solía ser. Antes de ES6, dependíamos de las funciones callback. Si eran necesarias múltiples callbacks (y frecuentemente lo eran), la anidación nos llevaría a escribir código que era muy difícil de leer, escalar y corregir; un fenómeno que se conoce como el "callback hell" (infierno de retro-llamadas) o "pyramid of doom"! (pirámide de la condena).

Las promesas fueron introducidas en ES6 (ES2015) para resolver el problema del callback hell, y ES8 (ES2018) vio la introducción de async ... await , dos palabras clave que simplificarían aún mas la funcionalidad asíncrona. Pero para nuestros propósitos, el método más crítico basado en promesas es fetch() .

El método Fetch

Este método ha estado disponible desde la versión 40 de Chrome, y es una alternativa más fácil de usar que XMLHttpRequest() .  

fetch() devuelve una promesa y por lo tanto podemos usar el método "then" para procesar el resultado.

Puedes agregar fetch a un método dentro de tu componente clase en React, de esta manera:

fetchPostData() {  
     fetch(`http://localhost/yoursitename/wp-json/wp/v2/movies?per_page=100`)  	  
    .then(response => response.json())  
    .then(myJSON => {  
     	// Logic goes here
     });
}

En el código de arriba dos cosas son importantes:

  • Primero, estamos llamando una URL con el filtro ?per_page=100 adjunto al final. Por defecto, WordPress sólo muestra 10 items por página y frecuentemente me encuentro queriendo incrementar ese límite.
  • Segundo, antes de procesar nuestros datos, estamos usando el método  .json(). Este es utilizado principalmente en relación con fetch(), y devuelve los datos como una promesa además de hacer "parse" al texto de la propiedad body en formato JSON.

En la mayoría de los casos, vamos a querer correr esta función tan pronto como nuestro componente React haya sido montado (mounted), y esto lo podemos especificar usando el método  componentDidMount() :

componentDidMount() {  this.fetchPostData();}

Manejando promesas

Una vez que se a devuelto una promesa, tienes que tener cuidado en manejarla en el contexto adecuado.

Cuando intenté usar promesas por primera vez, pasé un rato intentando pasar datos a las variables que se encontraban fuera del scope (ámbito) de la promesa. Aquí algunas buenas regla a seguir:

  • En React la mejor manera de usar promesas es mediante el uso del estado. Puedes utilizar  this.setState() para pasar los datos de la promesa al estado de tu componente.
  • Lo mejor es procesar, ordenar y re-agrupar tus datos dentro de una serie de métodos  then() encadenados al fetch() inicial. Una vez que se complete el proceso, es una buena práctica agregar los datos al estado dentro de tu método  then() final.
  • Si quieres llamar cualquier función adicional para procesar tu promesa (incluso dentro de render()) es buena práctica prevenir que la función corra hasta que la promesa se haya resuelto.
  • Así que, por ejemplo, si pasas tu promesa a  this.state.data , puedes incluir un condicional dentro del cuerpo de cualquiera de las funciones que dependen de ella, como te muestro abajo. Esto puede prevenir molesto comportamiento no deseado!
myPromiseMethod() {  if (this.state.data) {    // process promise here   } else {    // what to do before the fetch is successful  }}

Un ejemplo en React

Digamos que queremos traer  name, description, featured_image y genre del custom post type que definimos en la parte 1.

En el siguiente ejemplo, tomaremos esos 4 elementos para cada película y los mostraremos (render) en pantalla.

El siguiente bloque de código podría parecer intimidante, como es frecuente en los tutoriales React.  Pero espero que parezca mucho más simple cuando te lo explique más a detalle.

constructor(props)

En este método, llamamos super(props), para definir nuestro estado inicial (un objeto  data vacío) y uniremos (bind) tres nuevos métodos:

  • fetchPostData()
  • renderMovies()
  • populatePageAfterFetch()

componentDidMount()

Queremos traer nuestros datos tan pronto como el componente se haya montado, así que llamaremos fetchPostData() aquí.

fetchPostData()

Traemos el JSON desde nuestro URL, pasando .json() en el primer método .then() .

En el segundo método  .then() extraemos los cuatro valores que queremos para cada película que hemos traído y luego las agregamos al objeto newState .

Entonces podemos usar this.setState(newState) para agregar esta información a   this.state.data .

renderMovies()

El condicional  if (this.state.data) significa que la función correrá solamente una vez que los datos hayan sido extraídos (fetched).

Aquí, tomamos un arreglo de todas nuestras películas extraídas de  this.state.data  y lo pasamos a la función  populatePageAfterFetch() .

populatePageAfterFetch()

En esta función preparamos los datos para que cada película sea "renderizada" (mostrada en pantalla). Esto debería ser sencillo para cualquiera que haya utilizado JSX; con un potencial obstáculo.

El valor de  movie.description no es texto simple sino HTML. Para mostrar esto podemos usar  dangerouslySetInnerHTML={{__html: movie.description}} .

Nota: La razón por la que esto es potencialmente "peligroso" es que si tus datos fueran interceptados y recibieran una inyección de scripts XSS, este podría ser también  consumido y a su vez interpretado por el navegador de los usuarios. Como estamos usando nuestro propio servidor/CMS en este artículo, no deberíamos preocuparnos. Pero si quieres sanear (sanitize) tu HTML, dale un vistazo a DOMPurify.  

render()

Finalmente, controlaremos donde aparecerán nuestros datos renderizados al llamar el método renderMovies() dentro de las etiquetas HTML <div> seleccionadas. Ahora hemos extraído datos desde nuestro sitio WordPress exitosamente y los hemos mostrado en pantalla!

Conclusión

Espero que este artículo haga que el proceso de conectar un front-end React con Wordpress sea tan sencillo y libre de frustraciones como sea posible.

Como muchas cosas en la programación, lo que puede lucir intimidante pronto empezará a sentirse como instinto natural!

Estoy muy interesado en conocer tus propias experiencias usando WordPress como un CMS headless y estaría feliz de responder cualquiera de tus preguntas en los comentarios.