How to use Drupal 8 as a backend for React.js (Drupal 8, ES2015 (w/ Babel) and React.js)
Today I'm going to show you how to display a Drupal 8 block with recent comments. Displayed dynamically using React.js.
I have a vanilla Drupal site installed using the Standard profile so I have the Article content type with comments, which I'm going to use for this project.
Before we go further let's look at the prerequisites:
Let's get started.
First I'm going to create the folder called drupal_block_reactive inside my modules/custom folder, then I'll put my .info.yml file inside the folder.
drupal_block_reactive.info.yml
name: Recent comments (React)
type: module
description: 'Latest comments block with React.js'
package: Development
version: '8.x-1.x-dev'
core: '8.x'
dependencies:
- block
- comment
- node
- rest
- user
I have Rest module as a required module here since I'm going to crate a View with the Rest display to get comments so note that the urls will be "/api/comments". So this will be the only connection to fetch data from the backend.
Now let me add my Views export here so you can import it using Drupal 8 configurations import.
views.view.recent_comments_react.yml
langcode: en
status: true
dependencies:
module:
- comment
- node
- rest
- user
id: recent_comments_react
label: 'Recent comments react'
module: views
description: 'Recent comments.'
tag: default
base_table: comment_field_data
base_field: cid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access comments'
cache:
type: tag
query:
type: views_query
exposed_form:
type: basic
pager:
type: some
options:
items_per_page: 10
offset: 0
style:
type: html_list
options:
grouping: { }
row_class: ''
default_row_class: true
type: ul
wrapper_class: item-list
class: ''
row:
type: fields
options:
default_field_elements: true
inline:
subject: subject
changed: changed
separator: ' '
hide_empty: false
relationships:
node:
field: node
id: node
table: comment_field_data
required: true
plugin_id: standard
fields:
subject:
id: subject
table: comment_field_data
field: subject
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: false
ellipsis: false
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
plugin_id: field
entity_type: comment
entity_field: subject
changed:
id: changed
table: comment_field_data
field: changed
relationship: none
plugin_id: field
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
type: timestamp_ago
settings:
future_format: '@interval hence'
past_format: '@interval ago'
granularity: 2
entity_type: comment
entity_field: changed
cid:
id: cid
table: comment_field_data
field: cid
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: comment
entity_field: cid
plugin_id: field
filters:
status:
value: true
table: comment_field_data
field: status
id: status
plugin_id: boolean
expose:
operator: ''
group: 1
entity_type: comment
entity_field: status
status_node:
value: true
table: node_field_data
field: status
relationship: node
id: status_node
plugin_id: boolean
expose:
operator: ''
group: 1
entity_type: node
entity_field: status
sorts:
created:
id: created
table: comment_field_data
field: created
relationship: none
group_type: group
admin_label: ''
order: DESC
exposed: false
expose:
label: ''
plugin_id: date
entity_type: comment
entity_field: created
cid:
id: cid
table: comment_field_data
field: cid
relationship: none
group_type: group
admin_label: ''
order: DESC
exposed: false
plugin_id: field
entity_type: comment
entity_field: cid
title: 'Recent comments'
empty:
area_text_custom:
id: area_text_custom
table: views
field: area_text_custom
relationship: none
group_type: group
admin_label: ''
label: ''
empty: true
content: 'No comments available.'
tokenize: false
plugin_id: text_custom
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- user.permissions
max-age: -1
tags: { }
rest_export_1:
display_plugin: rest_export
id: rest_export_1
display_title: 'REST export'
position: 3
display_options:
display_extenders: { }
path: api/comments
style:
type: serializer
options:
uses_fields: true
formats:
json: json
filters: { }
defaults:
filters: false
filter_groups: false
filter_groups:
operator: AND
groups:
1: AND
row:
type: data_field
options:
field_options:
subject:
alias: ''
raw_output: false
changed:
alias: ''
raw_output: false
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- request_format
- user.permissions
tags: { }
Okay the backend if ready now, let's add the library dependencies, so in Drupal 8 how do we do that is we use .libraries.yml to add any js dependencies.
drupal_block_reactive.libraries.yml
reactjs:
remote: https://github.com/facebook/react
version: 15.1.0
license:
name: BSD
url: https://github.com/facebook/react/blob/master/LICENSE
gpl-compatible: true
js:
# Non-minified version: development friendly, debugging is possible.
//cdn.jsdelivr.net/react/15.1.0/react.js: { type: external, minified: false }
//cdn.jsdelivr.net/react/15.1.0/react-dom.js: { type: external, minified: false }
component:
version: VERSION
js:
js/dist/index.js: {}
dependencies:
- core/drupal
So what this code does is, this will get the React.js 15.1.0 added together with component which is what I'm going to detail next.
React.js Components let you split the UI into independent, reusable pieces, so each comment will be a Component and Comment container will be another Component.
I'm gonna create two files for those two components inside js/src/components directory.
js/src/components/Comment.js
// Comment component.
class Comment extends React.Component {
render() {
return ( <div className = "Comment" ><span> {this.props.subject} < /span> | <span>{this.props.changed}</span ></div>);
}
}
export default Comment
js/src/components/CommentBox.js
import Comment from './Comment'
// CommentBox component.
class CommentBox extends React.Component {
constructor() {
super();
// Setting initial state.
this.state = {
comments: \[\]
}
}
// Data from a service.
\_fetchData() {
jQuery.ajax({
url: this.props.url,
dataType: 'json',
success: (comments) => this.setState({
comments
}),
error: (xhr, status, err) => {
console.error(this.props.url, status, err.toString());
}
});
}
// Gets data from state, returns a list components.
\_getComments() {
// Get the list of comments from the state.
const commentsList = this.state.comments;
// Return an array of sub-components.
if (commentsList.length > 0) {
return commentsList.map((comment) => {
return <Comment subject = {comment.subject} changed = {comment.changed} key = {comment.cid}/>
});
}
return ( <p> {Drupal.t('No comments.')} </p>
);
}
componentDidMount() {
this.\_fetchData();
setInterval(this.\_fetchData.bind(this), this.props.timeInterval);
}
render() {
const commentsNodes = this.\_getComments();
return ( <div className = "CommentBox" > {commentsNodes} </div>
);
}
}
export default CommentBox
Now we have components added, let's put them together in the gatekeeper index.js file.
js/src/index.js
/\*\*
\* @file
\* Attaching React component via Drupal behaviors..
\*/
import CommentBox from './components/CommentBox'
Drupal.behaviors.drupal\_block\_reactive = {
attach: (context) => {
// Render our component.
ReactDOM.render(
<CommentBox url='/api/comments' timeInterval={2000}/>,
document.getElementById('recent-comments-react')
);
}
};
Now let's add the package.json for the dependencies.
js/package.json
{
"name": "drupal-block-reactive",
"version": "1.0.0",
"description": "A drop of reactivity in Drupal 8",
"main": "gulpfile.js",
"private": true,
"author": "Kalin Chernev",
"url": "https://github.com/kalinchernev",
"repository": {
"type": "git",
"url": "https://github.com/kalinchernev/drupal\_block\_reactive.git"
},
"devDependencies": {
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"gulp": "3.9.1",
"browserify": "13.1.0",
"gulp-babel": "6.1.2",
"vinyl-source-stream": "1.1.0",
"babelify": "7.3.0"
}
}
js/gulpfile.js
I'm going to use Gulp to compile the JS.
const gulp = require('gulp');
const babel = require('gulp-babel');
const browserify = require('browserify');
const source = require('vinyl-source-stream');
gulp.task('default', () => {
return browserify({
entries: 'src/index.js',
extensions: \['.jsx'\],
debug: true
})
.transform('babelify', {
presets: \['es2015', 'react'\]
})
.bundle()
.pipe(source('index.js'))
.pipe(gulp.dest('dist'));
});
Now let's connect this JS in Drupal UI using a static HTML block.
src/Plugin/Block/RecentCommentsBlock.php
<?php
namespace Drupal\drupal_block_reactive\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'Recent comments (reactive)' block.
*
* @Block(
* id = "recent_comments_reactive",
* admin_label = @Translation("Recent comments (reactive)"),
* category = "Development"
* )
*/
class RecentCommentsBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return [
'#markup' => '<div id="recent-comments-react"></div>',
'#attached' => [
'library' => [
'drupal_block_reactive/reactjs',
'drupal_block_reactive/component',
],
],
];
}
Now everything is set up, you can go to the js folder and run npm install to install all the dependencies. Then run gulp to compile the JS.
Once those steps are completed you can enable the block in a sidebar or content region and add couple of comments to see it in action.
You can also find the complete code here in this GitHub repository.
© Heshan Wanigasooriya.RSS🍪 This site does not track you.