(don't link to me!) Embed a React App in Drupal 8 Paragraph

Madeline Streilein background

Madeline Streilein —

Savas Labs blog

Topics

Step 1: Create custom theme

Step 2: Install Paragraphs contrib module

Step 3: Create a paragraph

Step 4: Embed react app in theme

Step 5: Configure library

Step 6: Preprocess paragraph

Step 7: Attach library to paragraph template

RELATED READ

A React App: Progressively Decoupled

Step 1: Create Custom Theme

Within your Drupal 8 project, define a custom theme.

Step 2: Install and Enable Drupal Paragraphs

Drupal paragraphs is a contributed module that ...

We use composer to manage our dependencies, ___

Step 3: Create a Paragraph

Create paragraph in the UI

Once enabled, a paragraph can be create in the Admin UI under ???

Export configuration

If you are using Drupal command line tool, run drush cex to export the configuration from the UI into the codebase.

Override theme template

Copy the contents of project/web/modules/contrib/paragraphs/templates/paragraph.html.twig into project/web/themes/custom/test/templates/paragraphs/paragraph--[name of paragraph here].html.twig, and theme your paragraph below. 

{#
/**
 * @file
 * Default theme implementation to display a paragraph.
 *
 * Available variables:
 * - paragraph: Full paragraph entity.
 *   Only method names starting with "get", "has", or "is" and a few common
 *   methods such as "id", "label", and "bundle" are available. For example:
 *   - paragraph.getCreatedTime() will return the paragraph creation timestamp.
 *   - paragraph.id(): The paragraph ID.
 *   - paragraph.bundle(): The type of the paragraph, for example, "image" or "text".
 *   - paragraph.getOwnerId(): The user ID of the paragraph author.
 *   See Drupal\paragraphs\Entity\Paragraph for a full list of public properties
 *   and methods for the paragraph object.
 * - content: All paragraph items. Use {{ content }} to print them all,
 *   or print a subset such as {{ content.field_example }}. Use
 *   {{ content|without('field_example') }} to temporarily suppress the printing
 *   of a given child element.
 * - attributes: HTML attributes for the containing element.
 *   The attributes.class element may contain one or more of the following
 *   classes:
 *   - paragraphs: The current template type (also known as a "theming hook").
 *   - paragraphs--type-[type]: The current paragraphs type. For example, if the paragraph is an
 *     "Image" it would result in "paragraphs--type--image". Note that the machine
 *     name will often be in a short form of the human readable label.
 *   - paragraphs--view-mode--[view_mode]: The View Mode of the paragraph; for example, a
 *     preview would result in: "paragraphs--view-mode--preview", and
 *     default: "paragraphs--view-mode--default".
 * - view_mode: View mode; for example, "preview" or "full".
 * - logged_in: Flag for authenticated user status. Will be true when the
 *   current user is a logged-in member.
 * - is_admin: Flag for admin user status. Will be true when the current user
 *   is an administrator.
 *
 * @see template_preprocess_paragraph()
 *
 * @ingroup themeable
 */
#}
{%
  set classes = [
    'paragraph',
    'paragraph--type--' ~ paragraph.bundle|clean_class,
    view_mode ? 'paragraph--view-mode--' ~ view_mode|clean_class,
    not paragraph.isPublished() ? 'paragraph--unpublished',
  ]
%}

{% block paragraph %}
  <section {{ bem('cards', [], '', classes) }}>
    {% block content %}
      <div {{ bem('container', [], 'cards', []) }}>
        {{ content.field_section_title }}
        {{ content.field_description }}
        <div id="react-app" class="paragraph--body bg-whisperGray"></div>
      </div>
    {% endblock %}
  </section>
{% endblock paragraph %}

 

Step 4: Embed React App in Theme

We actually created the react app before our the repository for this site was set up, but you can tackle this step at any point along the process. 

We added our react app within project/web/themes/custom/test/scripts/react-app

The relevant code within App.js:

import React, from 'react';
import ReactDOM from 'react-dom';

const App = ({ uuid }) => {
  let data = drupalSettings.testReact[uuid];
  ...
};

Object.keys(drupalSettings.testReact || {}).forEach((uuid) => {
  ReactDOM.render(<App uuid={uuid} />, document.querySelector('#pg-' + uuid));
});

 

RELATED READ

The Evolution of a React Prototype

Step 5: Configure Library

project/web/themes/custom/test/test.libraries.yml

react-app:
  js:
    build/js/app.js: { minified: true }
  dependencies:
    - core/drupalSettings
    - test/vendors-app
vendors:
  js:
    build/js/vendors~global.js: { minified: true }
vendors-app:
  js:
    build/js/vendors~app.js: { minified: true }

 

Step 6: Utilize Drupal.settings via Paragraph Preprocessing

Include preprocess file in test.theme

 project/web/themes/custom/test/test.theme

<?php

/**
 * @file
 * Theme functions.
 */

include 'preprocess/paragraph.preprocess.inc';

 

Preprocess paragraph and attach id and data to Drupal settings

project/web/themes/custom/test/preprocess/paragraph.preprocess.inc

<?php

/**
 * @file
 * Theme preprocess function for paragraphs.
 */

/**
 * Implements hook_preprocess_paragraph().
 */
function custom_preprocess_paragraph(&$variables) {
  if (empty($variables['paragraph'])) {
    return;
  }
  /* @var \Drupal\paragraphs\Entity\Paragraph $paragraph */
  $paragraph = $variables['paragraph'];

  $variables['uuid'] = $paragraph->uuid();

  switch ($paragraph->bundle()) {
    case 'cards':
      // Add data to Drupal settings.
      $variables['#attached']['drupalSettings']['test'][$paragraph->uuid()] = $cards;
      break;
...
  }
}

 

Step 7: Attach Library to Paragraph Template

#
/**
 * @file
 * Default theme implementation to display a paragraph.
 *
 * Available variables:
 * - paragraph: Full paragraph entity.
 *   Only method names starting with "get", "has", or "is" and a few common
 *   methods such as "id", "label", and "bundle" are available. For example:
 *   - paragraph.getCreatedTime() will return the paragraph creation timestamp.
 *   - paragraph.id(): The paragraph ID.
 *   - paragraph.bundle(): The type of the paragraph, for example, "image" or "text".
 *   - paragraph.getOwnerId(): The user ID of the paragraph author.
 *   See Drupal\paragraphs\Entity\Paragraph for a full list of public properties
 *   and methods for the paragraph object.
 * - content: All paragraph items. Use {{ content }} to print them all,
 *   or print a subset such as {{ content.field_example }}. Use
 *   {{ content|without('field_example') }} to temporarily suppress the printing
 *   of a given child element.
 * - attributes: HTML attributes for the containing element.
 *   The attributes.class element may contain one or more of the following
 *   classes:
 *   - paragraphs: The current template type (also known as a "theming hook").
 *   - paragraphs--type-[type]: The current paragraphs type. For example, if the paragraph is an
 *     "Image" it would result in "paragraphs--type--image". Note that the machine
 *     name will often be in a short form of the human readable label.
 *   - paragraphs--view-mode--[view_mode]: The View Mode of the paragraph; for example, a
 *     preview would result in: "paragraphs--view-mode--preview", and
 *     default: "paragraphs--view-mode--default".
 * - view_mode: View mode; for example, "preview" or "full".
 * - logged_in: Flag for authenticated user status. Will be true when the
 *   current user is a logged-in member.
 * - is_admin: Flag for admin user status. Will be true when the current user
 *   is an administrator.
 *
 * @see template_preprocess_paragraph()
 *
 * @ingroup themeable
 */
#}
{{ attach_library('test/react-app') }}
{%
  set classes = [
    'paragraph',
    'paragraph--type--' ~ paragraph.bundle|clean_class,
    view_mode ? 'paragraph--view-mode--' ~ view_mode|clean_class,
    not paragraph.isPublished() ? 'paragraph--unpublished',
  ]
%}

{% block paragraph %}
  <section {{ bem('cards', [], '', classes) }}>
    {% block content %}
      <div {{ bem('container', [], 'cards', []) }}>
        {{ content.field_section_title }}
        {{ content.field_description }}
        <div id="pg-{{ uuid }}" class="paragraph--body bg-whisperGray"></div>
      </div>
    {% endblock %}
  </section>
{% endblock paragraph %}