Implementing a React App and Connecting it to a Custom Drupal Block to Pull in Remote API Data

These days, Drupal core out of the box is sufficient for many website use cases and with the advent of Drupal 8 and 9, one can build a highly functional site with minimal contrib module add-ons. In the past few years, decoupled sites have also become quite popular which usually means a separate front-end framework such as Gatsby / React that queries data from a Drupal data endpoint, typically using something like GraphQL as middleware.

But there are also points in between where you might do something like "progressive decoupling", such as a custom block that is built with React and integrated right inside Drupal. At my current job, I was recently tasked with needing to pull in data from a remote API and have it render as a custom Drupal block. I decided upon using React for this task utilizing the Axios library, a promise based HTTP client for the browser and node.js, which is superb at pulling data from remote APIs.

Basic recipe

Here is an outline of the basic "ingredients" I used for this project.

  1. A custom Drupal module, react_jobs_teaser that will contain our React app, a custom block, and a Twig template for the block
  2. The React app pulls in data from the remote API via Axios
  3. Create a custom Drupal block in react_jobs_teaser/src/Plugin/Block/ReactJobsTeaserBlock.php
  4. Create a Twig template for the custom block with an ID that the React app will target viaReactDOM.render()
  5. A Drupal field that is rendered in the theme that the React app will target so as to make the API data contextual for each node
  6. Render the block in a theme template using Twig Tweak
  7. Expose the custom Drupal field data to React in the same theme Twig template

Getting started: the React app

The basic idea here is to build the React app and test it inside its own environment and then wire it up to Drupal for seamless integration. For my project, I used create-react-app which is a quick way to get up and running with a simple one page React site. We pull data from the USA Jobs API. Here is the code in the React App that pulls the data from the API.

useEffect(() => {
  const getJobs = async () => {
    try {
      const res = await axios.get(`https://data.usajobs.gov/api/Search?Organization=${agencyCode || 'BR'}`, axiosConfig,

    // Define the jobs array from the api.
    const allJobsData = res.data.SearchResult.SearchResultItems
  }
  catch (err) {
    console.log(err.message)
  }
}
getJobs()
  }, [])

Note the key part of the code above, ${agencyCode || 'BR'}. agencyCode pulls from our Drupal field so that for any given “Agency” node, the code will be different and therefore contextual for that agency node. We have a fallback, BR just in case there is no agency code present.

To test this right within React even before we go to Drupal, we can use React’s public/index.html file.

<div id="agency-code" data-ref="BR564"></div>

If you switch the data-ref code while React is running, you will see the data dynamically change on the page in real time. That is a good test for what's ahead on the Drupal side when we actually pull in data from a Drupal field that React will query.

On the React side, it’s permissible to use traditional vanilla JS to query the code since it will be outside the React DOM.

const agencyElement = document.getElementById('agency-code')
const agencyCode = agencyElement.dataset.ref

Noting that agencyElement.dataset.ref matches up to data-ref=“…”

The custom Drupal module

There is nothing overtly fancy with the Drupal module. The key part in the custom block is:

  /**
   * {@inheritdoc}
   */
  public function build() {
    // Custom React Jobs Teaser Block.
    $build = [];
    // Set the template name.
    $build['#theme'] = 'react_jobs_teaser';

    return $build;
  }

}

And then you just add a hook_theme in the module file to match for the template.

/**
 * Implements hook_theme().
 */
function react_jobs_teaser_theme($existing, $type, $theme, $path): array {
  // Custom theme calls for templates within this module in /templates.
  return [
    'react_jobs_teaser' => [
      'variables' => [
        'title' => '',
      ],
    ],
  ];
}

Then in the module’s template file, react-jobs-teaser.html.twig, we have:

{{ attach_library('react_jobs_teaser/usa_jobs_app') }}
<div id="jobs_teaser_app"></div>

The id of jobs_teaser_app matches what is on the React side for ReactDOM.render() in the app’s index.js file.

Drupal Theming

Now over in our theme's custom node template, we render the agency code field as:

{# Set the agency code. #}
{% set agency_code = content.field_agencycode | field_value %}
<div class="visually-hidden" id="agency-code" aria-hidden="true" data-ref="{{ agency_code }}"></div>

The final step is to render the custom jobs block using Twig Tweak in the same template like so:

 {{ drupal_block('react_jobs_teaser') }}

From here, the React app will query the agency code and render the contextual API data right within the custom Drupal block. The integration and HTML from React is seamless and ends up being part of the Drupal DOM.

Summary

Of course there are probably many other ways to accomplish this same task and I would venture to say that you could even build the entire app inside Drupal using PHP and query the jobs API that way. Using the React / Drupal hybrid method was a great learning experience and a lot of fun as well, especially considering my personal leaning toward front-end engineering.

Resources

Tags