How to use Utterances with React

  • React
  • JavaScript

Ahoy! In this post I’ll be explaining how I used the fantastic Utteranc.es 🔮 with React.

On the below links you’ll find an example site built with Gatsby along with a preview, and for good measure I’ve also included a link to the “comments repo” which I’ve used to capture issues/comments.

Example

What are Utterances?

I’ve lifted this straight from https://utteranc.es/

A lightweight comments widget built on GitHub issues. Use GitHub issues for blog comments, wiki pages and more!

This sounds like the perfect solution for anyone wishing to quickly add comments to a site, and if you look at what’s required to get going, it’s pretty straightforward.

<script
  src="https://utteranc.es/client.js"
  repo="[ENTER REPO HERE]"
  issue-term="pathname"
  theme="github-light"
  crossorigin="anonymous"
  defer
></script>

Adding the Utterances Script

But… in React adding a script element isn’t quite that easy. If you were to use the above in React nothing would happen. To note: you’d also see a warning about crossorigin needing to be crossOrigin, but that aside, the Utterances script wouldn’t load, and no issues/comments would be returned and rendered on the page.

React and Scripts

Firstly, React uses innerHtml to make changes to the DOM and because of this, and inline with the HTML spec, browsers must not execute scripts tags set via the use of innerHTML. Secondly, each time a component mounts or a re-render occurs due to a state change, React would be adding and executing this script over and over again, 🥴.

React Hooks and Scripts

Luckily however, React does provide a bit of escape hatch. When using one of React’s life cycle methods useEffect with an empty dependency array, you are able to perform an action only once.

Here’s an “only once” example.

When the component mounts it’ll fire off the console.log and any other changes triggered by state won’t trigger this “side effect”.

// src/components/utterances-comments.js

import React, { useEffect } from 'react';

const UtterancesComments = () => {
  useEffect(() => {
    console.log('only once');
  }, []);

  return null;
};

export default UtterancesComments;

Using this same technique you can manually create a script tag using good old fashioned vanilla JavaScript and then manually append it to a DOM element wherever you like.

React Hooks and Utterances

In the below snippet I’m using the same “only once” useEffect to create a new script element and append it to a div that is returned by Jsx.

// src/components/utterances-comments.js

import React, { useEffect, useRef } from 'react';

const UtterancesComments = () => {
  const ref = useRef();

  useEffect(() => {
    const script = document.createElement('script');

    const config = {
      src: 'https://utteranc.es/client.js',
      repo: '[ENTER REPO HERE]',
      'issue-term': 'pathname',
      theme: 'github-light',
      crossOrigin: 'anonymous',
      defer: true
    };

    Object.entries(config).forEach(([key, value]) => {
      script.setAttribute(key, value);
    });

    setTimeout(() => {
      ref.current.append(script);
    }, 300);
  }, []);

  return <div ref={ref} />;
};

export default UtterancesComments;

Utterances Component

There’s a few things going on in the above snippet, so lemme talk you through my approach. You can find the src for the complete component here: utterances-comments.js. The completed component is pretty much the same as the below, but I’ve added a few props, defaultProps and PropTypes to make it a little easier to configure and safer to use in production.

useRef

I’m using useRef from React to store a reference to the div element that I’ll later return in Jsx, more on that in a moment.

const ref = useRef();

createElement

Nothing more than good old fashioned vanilla JavaScript to create a new const called script and the creation of a new script HTML element.

const script = document.createElement('script');

config

This object is used to hold key value pairs for the attributes that will be added to the newly created HTML script element. The docs do a really good job at explaining what all the config options are, they’re interactive too, so I won’t dive any deeper into that here.

const config = {
  src: 'https://utteranc.es/client.js',
  repo: '[ENTER REPO HERE]',
  'issue-term': 'pathname',
  theme: 'github-light',
  crossOrigin: 'anonymous',
  defer: true
};

setAttribute

I’ve used Object.entries to “loop” over each key in the config object and have destructured both the key and the value. Pop a console.log in and try it yourself.

Object.entries(config).forEach(([key, value]) => {
  console.log(key, value);
  script.setAttribute(key, value);
});

This would result in something similar to the below.

src https://utteranc.es/client.js
utterances-comments.js:20 repo [ENTER REPO HERE]
utterances-comments.js:20 issue-term pathname
utterances-comments.js:20 theme github-light
utterances-comments.js:20 crossOrigin anonymous
utterances-comments.js:20 defer true

I can now use the key value pairs to set the script attributes using setAttribute.

The complete script element would then look something like this.

<script
  src="https://utteranc.es/client.js"
  repo="[ENTER REPO HERE]"
  issue-term="pathname"
  theme="github-light"
  crossorigin="anonymous"
  defer="true"
></script>

append

And lastly I use append to, well, append the script element to the ref I defined earlier.

setTimeout(() => {
  ref.current.append(script);
}, 300);

I did notice during testing however that if I didn’t use a setTimeout the code occasionally errored.

In my example repo I’ve used Gatsby’s new Head API to set elements in the document head, and I wrote a little more about a common migration pattern from React Helmet in this post: How to use Gatsby’s Head API with MDX

I should also point out that there is a node module for implementing Utterances in React, It’s called utterances-react which, rather annoyingly I didn’t discover until after I’d scratched my head getting this to work! It hasn’t been updated for a few years though, so I don’t know if it still works.

Gatsby Script API

As you might have heard, Gatsby recently released the Script API, I wrote a little more about how I used it to add Google Analytics to a Gatsby site: How to use Gatsby’s Script API with Google Analytics, but…

Script API isn’t quite the right tool for the job here. The main reason is that when using Script API the way I have in the Utterances Component, it won’t allow me to append it to a target.

Instead, Script API always adds the script element to the bottom of the HTML <body />. This is probably what you’d want in most cases, but you might like the ability to purposefully define where in your page the issues/comments are displayed.

Using the ref approach as I explained earlier allows me to have more control over the final position, or location in the DOM of my script. Arguably, adding third-party scripts in the middle of your page isn’t the best idea in case they block more important content for loading but, #YOLO.

Community!

I’m gonna go ahead and implement what I’ve shown here in my site for a few key reasons.

  1. It’s cool! I really like Utterances, and the only way to fully take it for a test drive is to use it. My site, if nothing else is a proving ground for how well Gatsby can integrate with just about anything.
  2. I’m always looking to improve my content. And whilst I appreciate the DM’s, they’re locked away and others can’t see your questions or feedback. This sometimes leads to the same question being asked multiple times and I must admit, I’m not the best at managing my Twitter DMs.
  3. I’m hoping we can all meet more folks working in this space, by leaving a comment, or asking a question, others can see you, and potentially help you better than I can.
  4. Promote yourself! If you’ve written something similar or are working on something you’d like to share, add a link in the comments. I have no problem with self-promotion. In fact, I encourage it.

Just keep it “family-friendly” — Please!

Thanks friends, see you around!