Artigo original: How to Create a Modern Web App Using WordPress and React

Escrito por: Bret Cameron

Quer as vantagens de uma aplicação de página única (do inglês, single-page application, ou SPA) moderna feita em React, mas precisa de um back-end que pareça familiar? Neste artigo, veremos como configurar a API REST do WordPress, incluindo tipos e campos de publicações personalizados, e como buscar esses dados dentro do React.

Recentemente, eu estava trabalhando em uma aplicação em React para um cliente e ele me fez uma pergunta: "podemos usá-la com o WordPress?"

Desde o final de 2015, a resposta a essa pergunta é "sim". No entanto, as etapas necessárias para se criar um site dissociado e funcional podem não ser óbvias, especialmente para aqueles que não estão familiarizados com o WordPress nem com o React.

Na minha jornada para criar aplicações funcionais, encontrei um punhado de obstáculos complexos e, neste artigo, explicarei como evitá-los. Também vou compartilhar várias dicas e truques que aprendi pelo caminho!

Conteúdo

Parte 1: informações básicas

Parte 2: WordPress

Parte 3: React

Um exemplo funcional em React

Conclusão

Parte 1: informações básicas

1_V5SV3AAoVrt9qjiWsTabbg

O que é um CMS headless?

No passado, usar um CMS como o WordPress significava que você tinha que construir seu front-end usando PHP.

Agora, com o CMS headless (em português, algo como "sem cabeça"), você pode construir seu front-end com as tecnologias que desejar. Isso se deve à separação do front-end e do back-end por meio de uma API. Se você quiser criar uma SPA usando React, Angular ou Vue, controlando o conteúdo por meio de um CMS como o WordPress, você pode!

O que preciso saber para acompanhar o tutorial?

Você vai tirar o máximo de proveito desse artigo se tiver:

  • algum conhecimento de como funciona um CMS como o WordPress, um pouco de PHP e uma ideia sobre como configurar um projeto básico do WordPress no seu computador;
  • um entendimento de JavaScript, incluindo recursos do ES6+ e sintaxe de classes em React.

Principais siglas

A programação é cheia de jargões, mas eles agilizam muito a discussão sobre alguns dos conceitos neste artigo. Eis um resumo rápido dos termos que vamos usar:

  • CMS – sistema de gestão de conteúdo (Content Management System, em inglês).  Alguns exemplos são WordPress, Drupal, Joomla e Magneto.
  • SPA – aplicação de página única (Single-Page Application, em inglês).  Em vez de recarregar cada página na sua totalidade, uma aplicação SPA carrega o conteúdo dinamicamente. O código fundamental (HTML, CSS e JavaScript) do site é carregado apenas uma vez. Alguns exemplos são React, Vue e Angular.
  • API – interface de programação de aplicações (Application Programming Interface, em inglês).  Em termos simples, uma série de definições que um serviço fornece para permitir que você receba e use seus dados.  O Google Maps tem uma. O Medium tem uma. Agora, todo site do WordPress vem com uma API embutida.
  • REST – transferência de estado representacional (Representational State Transfer, em inglês).  Um estilo de arquitetura da web baseado nos métodos de solicitação do HTTP: GET , PUT , POST e DELETE. A API embutida do WordPress é uma API REST ou "RESTful".
  • HTTP – protocolo de transferência de hipertexto (HyperText Transfer Protocol, em inglês).  O conjunto de regras utilizadas para transferir dados através da web. Ele é especificado no início dos URLs como http ou https (a versão segura).
  • JSON – notação de objeto JavaScript (JavaScript Object Notation, em inglês)  Apesar de ser derivado do JavaScript, ele é um formato para armazenamento e transferência de dados que é independente da linguagem.

Neste artigo, estamos usando o WordPress como nosso CMS. Isso significa programar nosso back-end em PHP e usar a API REST do WordPress para entregar dados JSON ao nosso front-end.

Onde posso ver os dados em JSON do WordPress?

Antes de pôr as mãos na massa, uma nota rápida sobre onde você pode encontrar os dados em JSON em seu site do WordPress. Hoje em dia, todo site feito com WordPress tem dados em JSON disponíveis (a menos que o administrador do site tenha desativado ou restringido o acesso a eles). Você pode ver o JSON principal de um site do WordPress anexando  /wp-json  ao nome de domínio raiz.

Então, por exemplo, você pode dar uma olhada no JSON do site WordPress.org visitando  https://wordpress.org/wp-json. Como alternativa, se você estiver executando um site do WordPress localmente, você pode ver seu JSON acessando localhost/nome-do-seu-site/wp-json.

Para acessar os dados das suas publicações, escreva  localhost/nome-do-seu-site/wp-json/wp/v2/posts. Para um formato de publicação personalizado, troque para o novo formato (por exemplo, movies – filmes) em vez de posts. O que agora parece ser um bloco de texto ilegível é exatamente o que nos permitirá usar o WordPress como um CMS headless!

Parte 2: WordPress

1_jUVbqOjhcy-68oCBkIlm2Q

Para configurar sua API REST, a maior parte do que você vai fazer acontecerá no seu arquivo functions.php. Partirei do pressuposto de que você sabe configurar um projeto com WordPress e acessá-lo usando localhost, mas, se precisar de ajuda com isso, eu recomendo este artigo (em inglês, mas é o que eu usei para começar a programar com o WordPress).

Para a maioria dos projetos, você vai querer usar um tipo de publicação personalizado. Então, comecemos configurando um.

Como adicionar um tipo de publicação personalizado

Digamos que nosso site seja sobre filmes e queremos um tipo de publicação chamado 'movies' (em português, filmes). Primeiro, queremos garantir que nosso tipo de publicação 'movies' seja carregado o mais rápido possível. Então, vamos anexá-lo ao hook init utilizando add_action:

add_action( 'init', 'movies_post_type' );

Estou usando movies_post_type(), mas você pode chamar sua função do que quiser.

Em seguida, vamos registrar 'movies' como um tipo de publicação, usando a função register_post_type().

O próximo trecho de código pode parecer intimidador, mas é relativamente simples: nossa função requer muitos argumentos embutidos para controlar a funcionalidade do seu novo tipo de publicação. A maioria deles é autoexplicativo. Vamos armazenar esses argumentos no array $args.

Um dos argumentos, labels, pode receber diversos argumentos diferentes em si. Assim, o separamos em um outro array, $labels, o que nos dá:

<?php // Registrar o tipo de publicação personalizada: filmes

function movies_post_type() {

	$labels = array(
		'name'                  => _x( 'Movies', 'Post Type General Name', 'text_domain' ),
		'singular_name'         => _x( 'Movie', 'Post Type Singular Name', 'text_domain' ),
		'menu_name'             => __( 'Movies', 'text_domain' ),
		'name_admin_bar'        => __( 'Movie', 'text_domain' ),
		'archives'              => __( 'Item Archives', 'text_domain' ),
		'attributes'            => __( 'Item Attributes', 'text_domain' ),
		'parent_item_colon'     => __( 'Parent Item:', 'text_domain' ),
		'all_items'             => __( 'All Items', 'text_domain' ),
		'add_new_item'          => __( 'Add New 3D Printer', 'text_domain' ),
		'add_new'               => __( 'Add New', 'text_domain' ),
		'new_item'              => __( 'New Movie', 'text_domain' ),
		'edit_item'             => __( 'Edit Movie', 'text_domain' ),
		'update_item'           => __( 'Update Movie', 'text_domain' ),
		'view_item'             => __( 'View Item', 'text_domain' ),
		'view_items'            => __( 'View Items', 'text_domain' ),
		'search_items'          => __( 'Search Item', 'text_domain' ),
		'not_found'             => __( 'Not found', 'text_domain' ),
		'not_found_in_trash'    => __( 'Not found in Trash', 'text_domain' ),
		'featured_image'        => __( 'Featured Image', 'text_domain' ),
		'set_featured_image'    => __( 'Set featured image', 'text_domain' ),
		'remove_featured_image' => __( 'Remove featured image', 'text_domain' ),
		'use_featured_image'    => __( 'Use as featured image', 'text_domain' ),
		'insert_into_item'      => __( 'Insert into item', 'text_domain' ),
		'uploaded_to_this_item' => __( 'Uploaded to this item', 'text_domain' ),
		'items_list'            => __( 'Items list', 'text_domain' ),
		'items_list_navigation' => __( 'Items list navigation', 'text_domain' ),
		'filter_items_list'     => __( 'Filter items list', 'text_domain' ),
	);

	$args = array(
		'label'                 => __( 'Movie', 'text_domain' ),
		'description'           => __( 'Our featured films.', 'text_domain' ),
		'labels'                => $labels,
		'supports'              => array( 'title', 'editor', 'thumbnail'),
		'taxonomies'            => array( '' ),
		'hierarchical'          => false,
		'public'                => true,
		'show_in_rest'          => true,
		'show_ui'               => true,
		'show_in_menu'          => true,
		'menu_position'         => 5,
		'show_in_admin_bar'     => true,
		'show_in_nav_menus'     => true,
		'can_export'            => true,
		'has_archive'           => true,
		'exclude_from_search'   => false,
		'publicly_queryable'    => true,
		'capability_type'       => 'page',
	);

  register_post_type( 'movies', $args );
  
}

add_action( 'init', 'movies_post_type', 0 );

Dois dos argumentos mais importantes são "supports" e "taxonomies", porque eles controlam quais dos campos de publicação nativos serão acessíveis no nosso novo tipo de publicação.

No código acima, optamos por apenas três "supports":

  • "title"– o título de cada publicação.
  • "editor"– o editor de texto principal, que usaremos para nossa descrição.
  • "thumbnail"– a imagem em destaque da publicação.

Para ver a lista completa do que está disponível, clique aqui para ver quais são os 'supports' e aqui para ver as 'taxonomies' (textos em inglês).

O site Generate WordPress também tem  uma ferramenta útil que ajuda a criar tipos de publicação personalizados, o que pode tornar o processo muito mais rápido.

Como alterar o título de espaço reservado

Se o placeholder (em português, espaço reservado) do título "enter title here" (em português, insira o título aqui) não se encaixar no seu tipo de postagem personalizado, você poderá editá-lo com uma função separada:

<?php // Troque o  placeholder do título por um título customizado

function change_title_text( $title ){
  $screen = get_current_screen();
  if ( 'movies' == $screen->post_type ) {
    $title = 'Enter title here';
  }
  return $title;
}

add_filter( 'enter_title_here', 'change_title_text' );

Como adicionar um campo personalizado ao seu tipo de postagem personalizado

Se você quiser um campo que não vem pré-definido pelo WordPress, o que pode fazer? Por exemplo, digamos que queremos um campo especial chamado "genre" (em português, gênero). Nesse caso, você precisará usar  add_meta_boxes().

Para isso, precisamos anexar uma nova função ao hook add_meta_boxes do WordPress:

add_action( 'add_meta_boxes', 'genre_meta_box' );

Dentro de nossa nova função, precisamos chamar a função add_meta_box()  do WordPress, assim:

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

Você pode ler mais sobre os argumentos dessa função aqui. Para nossos propósitos, a parte mais crítica é a função de callback, que nomeamos genre_meta_box_callback. Ela define o conteúdo real na caixa meta. Precisamos apenas de uma entrada de texto simples, então 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
};

Por fim, nosso campo personalizado não vai salvar seu valor a menos que seja conveniente. Para esse efeito, podemos definir uma nova função  save_genre()  e anexá-la ao hook save_post do WordPress:

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

Juntando tudo, o código usado para criar o campo personalizado deve ficar assim:

<?php // Add Custom Field Type: Genre

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

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
}

function save_genre(){
  global $post;
  update_post_meta($post->ID, "genre", 
  $_POST["genre"]);
}

add_action( 'add_meta_boxes', 'genre_meta_box' );
add_action( 'save_post', 'save_genre' );

Como disponibilizar campos personalizados como JSON

Nossas postagens personalizadas ficam disponibilizadas automaticamente como JSON. Para o nosso tipo de publicação "movies", os nossos dados em JSON podem ser encontrados em  localhost/nomedoseusite/wp-json/wp/v2/movies.

No entanto, nossos campos personalizados não fazem parte disso automaticamente e, portanto, precisamos adicionar uma função para garantir que eles também sejam acessíveis por meio da API REST.

Primeiro, precisamos anexar uma nova função ao hook rest_api_init :

add_action ('rest_api_init', 'registrar_genero_como_campo_rest');

Então, podemos usar a função incorporada register_rest_field(), assim:

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

Essa função recebe um array com os callbacks get e update. Porém, em um caso mais simples como esse, só precisamos especificar um 'get_callback':

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

Como um todo, este é o código necessário para registrar um campo personalizado.

<?php // Registrar um tipo de campo personalizado como um campo REST

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

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

add_action( 'rest_api_init', 'register_genre_as_rest_field' );

Como disponibilizar URLs de imagens em destaque como JSON

A API REST do WordPress não inclui URLs para as suas imagens em destaque nativamente. Para facilitar o acesso, você pode usar o seguinte código:

<?php // Add 'featured_image_url' to REST data

  function post_featured_image_json( $data, $post, $context ) {
  $featured_image_id = $data->data['featured_media'];
  $featured_image_url = wp_get_attachment_image_src( $featured_image_id, 'original' );

  if( $featured_image_url ) {
    $data->data['featured_image_url'] = $featured_image_url[0];
  }

  return $data;
}

add_filter( 'rest_prepare_movies', 'post_featured_image_json', 10, 3 );

O filtro rest_prepare_posts do WordPress é dinâmico, então podemos trocar "posts" por um tipo de publicação personalizada, como rest_prepare_movies.

Como restringir dados JSON visíveis

Estamos quase prontos para começar a extrair dados para a aplicação em React, mas ainda dá para fazer mais uma otimização rápida: limitar os dados disponibilizados.

Alguns dados vêm por padrão e é possível que você nunca precise deles no seu front-end. Se for o caso, podemos removê-los usando um filtro, como este:

<?php // Filter REST data

function filter_rest_data( $data, $post, $request ) {
  $_data = $data->data;
  $params = $request->get_params();
  if ( ! isset( $params['id'] ) ) {
    unset( $_data['date'] );
    unset( $_data['slug'] );
    unset( $_data['date_gmt'] );
    unset( $_data['modified'] );
    unset( $_data['modified_gmt'] );
    unset( $_data['guid'] );
    unset( $_data['type'] );
  };
  $data->data = $_data;
  return $data;
};

add_filter( 'rest_prepare_movies', 'filter_rest_data', 10, 3 );

Você pode achar os nomes dos tipos de dados acessando a parte /wp-json/wp/v2/filmes do seu site.

Com isso feito, assim que tiver adicionado alguns filmes usando o back-end do WordPress, teremos todo o necessário para começar a trazer dados para o React!

Parte 3: React

1_qKj1pMwgVFGtOID_wD1N-g

Para buscar dados externos em JavaScript, você precisa usar promises. Isso provavelmente influenciará a melhor maneira de estruturar seus componentes no React, e, no meu caso (converter um projeto React pré-existente), eu tive que reescrever boa parte do código.

Promises em JavaScript

As promises em JavaScript são usadas para manipular ações assíncronas: coisas que acontecem fora do passo a passo tradicional, ou seja, a ordem de execução "síncrona" (após o hoisting).

O bom é que JavaScript assíncrono é muito mais fácil hoje em dia. Antes da ES6, dependíamos de funções de callback. Se múltiplas callbacks fossem necessárias (e muitas vezes eram), o aninhamento levaria a um código que era muito difícil de ler, escalonar e depurar – um fenômeno às vezes chamado de "callback hell" ou "pirâmide da perdição"!

As promises foram introduzidas na ES6 (ou ES2015) para resolver esse problema, e a ES8 (ou ES2018) viu a introdução do async ... await, duas palavras-chave que simplificam ainda mais a funcionalidade assíncrona. Porém, para os nossos propósitos, o método mais importante que usa as promises é o fetch().

O método fetch

Esse método está disponível desde o Chrome 40 e é uma alternativa ao XMLHttpRequest(), sendo mais fácil de usar.

fetch() retorna uma promise e, portanto, é compatível com "then", ou seja, podemos usar o método then() para processar o resultado.

Você pode adicionar fetch a um método dentro de seu componente de classe do React, desta maneira:

fetchPostData() {  
    fetch(`http://localhost/yoursitename/wp-json/wp/v2/movies?per_page=100`)  
    .then(response => response.json())  
    .then(myJSON => {  
        // A lógica vai aqui
    });
}

No código acima, duas coisas são importantes:

  • Primeiro, estamos chamando um URL com o filtro  ?per_page=100 anexado ao final. Por padrão, o WordPress mostra apenas 10 itens por página. Muitas vezes, me vejo querendo aumentar esse limite.
  • Segundo, antes de processar os dados, usamos o método .json(). Esse método é utilizado principalmente em relação ao fetch(), onde ele retorna os dados como promise e analisa o corpo do texto como JSON.

Na maioria dos casos, queremos que essa função seja executada assim que o componente React for montado. Podemos especificar isso usando o método componentDidMount() :

componentDidMount() {  
    this.fetchPostData();
}

Manipulação de promises

Depois de devolver uma promise, você deve ter o cuidado de lidar com ela no contexto correto.

Na primeira vez que tentei usar promises, levei um tempo tentando passar os dados para variáveis fora do escopo da promise. Aí vão algumas regras básicas:

  • Em React, a melhor forma de usar promises é através do state (em português, estado). Você pode usar this.setState() para passar dados da promise para o state do seu componente.
  • É melhor processar, classificar e reorganizar seus dados em uma série de métodos then() após o fetch() inicial. Depois que todo o processamento estiver completo, a melhor prática é adicionar os dados ao state dentro do seu método then() final.
  • Se quiser chamar qualquer outra função adicional para processar a sua promise (inclusive dentro do render()), é boa prática prevenir que a função rode até que a promise seja resolvida.
  • Então, por exemplo, se estiver passando sua promise para this.state.data, você pode incluir uma condicional dentro do corpo de qualquer função que dependa dela, como no exemplo abaixo. Isso pode evitar comportamentos indesejados irritantes!
myPromiseMethod() {  
    if (this.state.data) {    
        // processe a promise aqui   
    } else {    
        // o que fazer antes do fetch ser concluído  
    }
}

Um exemplo funcional em React

Digamos que queremos trazer name, description, featured_image e gender (em português, nome, descrição, imagem em destaque e gênero, respectivamente) do tipo de publicação customizado do WordPress que definimos na parte 1.

No exemplo a seguir, vamos buscar esses quatro elementos para cada filme e renderizá-los.

Como tantas vezes acontece em tutoriais de React, o bloco de código a seguir pode parecer intimidador, mas espero que fique bem mais simples conforme o dividimos em partes.

import React, { Component } from 'react';

export default class Movies extends Component {
  
  constructor(props) {
    super(props);
    this.state = { data: {} };
  
    this.fetchPostData = this.fetchPostData.bind(this);
    this.renderMovies = this.renderMovies.bind(this);
    this.populatePageAfterFetch = this.populatePageAfterFetch.bind(this);
    
  }

  componentDidMount() {
    this.fetchPostData();
  }

  fetchPostData() {
    fetch(`http://localhost/yoursitename/wp-json/wp/v2/movies?per_page=100`)
      .then(response => response.json())
      .then(myJSON => {
        let objLength = Object.keys(myJSON).length;
        let newState = this.state;
      
        for (let i = 0; i < objLength; i++) {
          let objKey = Object.values(myJSON)[i].title.rendered;
          let currentMovie = newState.data[objKey];
          
          currentMovie = {};
          currentMovie.name = Object.values(myJSON)[i].title.rendered;
          currentMovie.description = Object.values(myJSON)[i].content.rendered;
          currentMovie.featured_image = Object.values(myJSON)[i]['featured_image_url'];
          currentMovie.genre = Object.values(myJSON)[i]['genre'];          
        }
      
      this.setState(newState);
      });
  }
  
  renderMovies() {
    if (this.state.data) {
      const moviesArray = Object.values(this.state.data)
      return Object.values(moviesArray).map((movie, index) => this.populatePageAfterFetch(movie, index));
    }
  }

  populatePageAfterFetch(movie, index) {
    if (this.state.data) {

      return (
        <div key={index} index={index}> 
          <h2 className="name">{movie.name}</h2> 
          <h3 className="genre">{movie.genre}</h3>
          <div className="description" dangerouslySetInnerHTML={{__html: movie.description}} />
          <img className="featured_image" src={movie.featured_image} alt={movie.name} />
        </div>
      )
    }
  }

  render() {
      return (
      <>
        <h1>Movies</h1>
        <div>{this.renderMovies()}</div>
      </>
      )
  }
}

constructor(props)

Nesse método, chamamos super(props), definimos o estado inicial (um objeto data vazio) e anexamos três métodos novos:

  • fetchPostData()
  • renderMovies()
  • populatePageAfterFetch()
constructor(props) {
    super(props);
    this.state = { data: {} };
  
    this.fetchPostData = this.fetchPostData.bind(this);
    this.renderMovies = this.renderMovies.bind(this);
    this.populatePageAfterFetch = this.populatePageAfterFetch.bind(this);
    
  }

componentDidMount()

Queremos baixar nossos dados assim que o componente estiver montado, então vamos chamar fetchPostData() aqui.

componentDidMount() {
    this.fetchPostData();
}

fetchPostData()

Buscamos o JSON do URL, passando .json() no primeiro método .then().

No segundo método .then(), extraímos os quatro valores que queremos para cada entrada de filme que recebemos e, em seguida, os adicionamos ao objeto newState.

Em seguida, usamos this.setState(newState) para adicionar essas informações ao this.state.data.

fetchPostData() {
    fetch(`http://localhost/yoursitename/wp-json/wp/v2/movies?per_page=100`)
      .then(response => response.json())
      .then(myJSON => {
        let objLength = Object.keys(myJSON).length;
        let newState = this.state;
      
        for (let i = 0; i < objLength; i++) {
          let objKey = Object.values(myJSON)[i].title.rendered;
          let currentMovie = newState.data[objKey];
          
          currentMovie = {};
          currentMovie.name = Object.values(myJSON)[i].title.rendered;
          currentMovie.description = Object.values(myJSON)[i].content.rendered;
          currentMovie.featured_image = Object.values(myJSON)[i]['featured_image_url'];
          currentMovie.genre = Object.values(myJSON)[i]['genre'];          
        }
      
      this.setState(newState);
      });
  }

renderMovies()

O if (this.state.data) condicional significa que a função só será executada depois que os dados forem obtidos.

Aqui, recebemos um array de todos os nossos filmes buscados de this.state.data e o passamos para a função populatePageAfterFetch().

renderMovies() {
    if (this.state.data) {
      const moviesArray = Object.values(this.state.data)
      return Object.values(moviesArray).map((movie, index) => this.populatePageAfterFetch(movie, index));
    }
  }

populatePageAfterFetch()

Nessa função, vamos preparar os dados de cada filme para serem renderizados. Isso deve ser simples para qualquer pessoa que tenha usado o JSX, mas com um obstáculo em potencial.

O valor de movie.description não é texto simples, mas marcação em HTML. Para ver o valor, podemos usar dangerouslySetInnerHTML={{__html: movie.description}}.

populatePageAfterFetch(movie, index) {
    if (this.state.data) {

      return (
        <div key={index} index={index}> 
          <h2 className="name">{movie.name}</h2> 
          <h3 className="genre">{movie.genre}</h3>
          <div className="description" dangerouslySetInnerHTML={{__html: movie.description}} />
          <img className="featured_image" src={movie.featured_image} alt={movie.name} />
        </div>
      )
    }
  }

Observação: o motivo disso ser potencialmente "perigoso" é que, se seus dados forem hackeados e injetados com scripts XSS maliciosos, eles também serão analisados. Como estamos usando nosso próprio servidor/CMS neste artigo, não precisamos nos preocupar. Porém, se você quiser higienizar seu HTML, dê uma olhada no DOMPurify.

render()

Por fim, controlamos onde os dados renderizados aparecerão chamando o método renderMovies() dentro das tags <div> escolhidas. Acabamos de baixar e exibir dados do nosso site WordPress com sucesso!

render() {
      return (
      <>
        <h1>Movies</h1>
        <div>{this.renderMovies()}</div>
      </>
      )
  }

Conclusão

No geral, espero que este artigo tenha tirado o máximo possível da dor do processo de conectar um front-end em React a um back-end do WordPress.

Como tantas outras coisas na programação, o que pode parecer intimidador em princípio logo se torna natural com a prática!