Adding ReCaptcha to React Comments

  • henok_mikre

    Henok Mikre

  • naod_yeheyes

    Naod Yeheyes

React Comments is a Disqus-like comment feature for Drupal 8. It is a much more beautiful alternative to the Ajax Comments module, which we had migrated to Drupal 8 as an initial solution. The only thing missing in React Comments for our purposes was ReCaptcha integration.

There are various ReCaptcha components for React. We tried a few and decided on react-recaptcha-google. We added that package to the project (yarn add react-recaptcha-google) and added an implementation of that component:

cd react_comments/js/src/
cat src/components/CommentReacaptcha.js
import React, { Component } from "react";
import "./CommentReacaptcha.css";
import { ReCaptcha } from "react-recaptcha-google";

class CommentReacaptcha extends Component {
  constructor(props, context) {
    super(props, context);
    this.onLoadRecaptcha = this.onLoadRecaptcha.bind(this);
    this.verifyCallback = this.verifyCallback.bind(this);
  }

  componentDidMount() {
    if (this.captchaDemo) {
      this.captchaDemo.reset();
    }
  }

  resetCaptcha() {
    this.captchaDemo.reset();
  }

  onLoadRecaptcha() {
    if (this.captchaDemo) {
      this.captchaDemo.reset();
    }
  }

  verifyCallback(recaptchaToken) {
    this.props.verified(recaptchaToken);
  }

  render() {
    let containerClasses = ["rc_comment-recap-container"];

    return (
      <div className={containerClasses}>
        <ReCaptcha
          ref={(el) => {
            this.captchaDemo = el;
          }}
          size="normal"
          render="explicit"
          sitekey="<our-site-key>"
          onloadCallback={this.onLoadRecaptcha}
          verifyCallback={this.verifyCallback}
        />
      </div>
    );
  }
}

export default CommentReacaptcha;

We imported the component to App.js:

cat src/App.js | awk 'NR >= 9 && NR <= 10'
import { loadReCaptcha } from "react-recaptcha-google";
import CommentReacaptcha from "./components/CommentReacaptcha";

We added state variable that will indicate whether captcha is verified or not:

cat src/App.js | awk 'NR >= 12 && NR <= 22'
class App extends Component {
  state = {
    currentUser: {
      hasPermission: () => false
    },
    comments: null,
    settings: null,
    loading: true,
    openMenu: null,
    captchaVerified: false,
  };

We added a callback method that will update the state variable:

cat src/App.js | awk 'NR >= 180 && NR <= 182'
  captchaVerified(token) {
    this.setState({captchaVerified: !!token})
  }

Added component to render:

cat src/App.js | awk 'NR >= 203 && NR <= 203'
<CommentReacaptcha verified={this.captchaVerified.bind(this)} />

Finally, added an attribute in CommentBox component to disable comment button if captcha is not verified:

cat src/components/CommentBox.js | awk 'NR >= 209 && NR <= 209'
{
  type === "comment" && user && userCanPostComments && (
    <button
      disabled={!this.props.captchaVerified}
      onClick={this.handlePost.bind(this)}
      className="rc_add-comment"
    >
      Post your comment{user.name && ` as ${user.name}`}
    </button>
  );
}

One last issue:

Once in a while, the browser would throw an error complaining something about grecaptcha.render not being a function. The fix is to check for that in the module. We first tried to patch the module with npm but there is a mismatch betweeen the version of react-recaptcha-google in npmjs.com and that in GitHub. So we forked the repo and added our own patch to it:

git clone git@github.com:henokmikre/react-recaptcha-google.git
cd react-recaptcha-google/
git checkout 2718b3675098a6b9856aaf1af4df57ed10df6011 -b v2
vi src/ReCaptcha.js
yarn install
yarn build
git commit -am "Added error handling for grecaptcha.render."

Here is the exact change (which was actually taken from another npm package appleboy/react-recaptcha):

diff --git a/src/ReCaptcha.js b/src/ReCaptcha.js
index 674e746..8df8a7a 100644
--- a/src/ReCaptcha.js
+++ b/src/ReCaptcha.js
@@ -37,7 +37,11 @@ const defaultProps = {
     badge: 'bottomright',
 };

-const isReady = () => typeof window !== 'undefined' && typeof window.grecaptcha !== 'undefined';
+const isReady = () =>
+  typeof window !== "undefined"
+  && typeof window.grecaptcha !== 'undefined'
+  // need to check for presence of render, because reCaptcha creates { ready } first
+  && !!window.grecaptcha.render;
git push origin v2

We then added the forked version of react-recaptcha-google in react_comments yarn.lock. A better approach would be to use a component based on ReCaptcha v3.

Last step was to rebuild the React. But we first need to make sure we have the build tools:

# Install nodejs
curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -
sudo yum install -y nodejs

# Install yarn
curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
sudo yum install -y yarn

# Install packages
yum install

Now we can build:

./build.php

That's all!

Resources