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.
- 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.
- 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.
- 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.
- 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!