Adding utterances to your React Blog

Adding utterances to your React Blog

Posted on 27th February 2022

4 min read

100 views

In this post I’ll go through how I added comments to my blog using utterances, which uses GitHub issues to store the comments so it’s really easy to setup.

First thing you need to have a public GitHub repository with the utterances app installed. In my case I have installed it in the repo of my blog.

Next create a component.

Comments.tsx

import type { FC } from "react";

const Comments: FC = () => {
  return <></>;
};

export default Comments;

After that, add a div as a container for the comments and also store it’s ref.

Comments.tsx

import type { FC } from "react";
import { useRef } from "react";

const Comments: FC = () => {
  const parentRef = useRef<HTMLDivElement>(null);

  return (
    <>
      <div ref={parentRef} />
    </>
  );
};

export default Comments;

Then we’ll add a <script> tag using an useEffect hook. utterances gives us the HTML to just add the <script> to our file, but we’ll need the cleanup function in the useEffect hook later.

Comments.tsx

import type { FC } from "react";
import { useRef, useEffect } from "react";

const Comments: FC = () => {
  const parentRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const parent = parentRef?.current;
    const script = document.createElement("script");

    script.setAttribute("src", "https://utteranc.es/client.js");
    script.setAttribute("repo", "akhila-ariyachandra/akhilaariyachandra.com");
    script.setAttribute("issue-term", "pathname");
    script.setAttribute("theme", "github-light");
    script.setAttribute("crossorigin", "anonymous");
    script.setAttribute("async", "true");

    parent?.appendChild(script);
  }, [parentRef]);

  return (
    <>
      <div ref={parentRef} />
    </>
  );
};

export default Comments;
Replace the value for repo with your own repository.

All we’re doing here is creating a <script> tag and adding it to the <div> container.

This will work fine as is but will create problems when running the blog in development mode with features like hot reloading and fast refresh. It will just keep adding multiple instances of utterances without removing the previous ones.

To fix this we can use the cleanup function of the useEffect hook to remove all children of the <div> container.

Comments.tsx

import type { FC } from "react";
import { useRef, useEffect } from "react";

const Comments: FC = () => {
  const parentRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const parent = parentRef?.current;
    const script = document.createElement("script");

    script.setAttribute("src", "https://utteranc.es/client.js");
    script.setAttribute("repo", "akhila-ariyachandra/akhilaariyachandra.com");
    script.setAttribute("issue-term", "pathname");
    script.setAttribute("theme", "github-light");
    script.setAttribute("crossorigin", "anonymous");
    script.setAttribute("async", "true");

    parent?.appendChild(script);

    return () => {
      while (parent?.firstChild) {
        parent?.removeChild(parent?.lastChild);
      }
    };
  }, [parentRef]);

  return (
    <>
      <div ref={parentRef} />
    </>
  );
};

export default Comments;

Now when the component rerenders, it will remove all the children of the container before running the script and displaying the comments again.

Since we have the cleanup function to remove the children on rerenders, we can also use it to remove the comments when the theme is switched if your site supports it.

In my site I use next-themes. If we add the theme variable to the useEffect hook’s dependency array we can run the cleanup function and script again when the theme changes.

Comments.tsx

import type { FC } from "react";
import { useRef, useEffect } from "react";
import { useTheme } from "next-themes";

const Comments: FC = () => {
  const parentRef = useRef<HTMLDivElement>(null);
  const { theme } = useTheme();

  useEffect(() => {
    const parent = parentRef?.current;
    const script = document.createElement("script");

    script.setAttribute("src", "https://utteranc.es/client.js");
    script.setAttribute("repo", "akhila-ariyachandra/akhilaariyachandra.com");
    script.setAttribute("issue-term", "pathname");
    script.setAttribute(
      "theme",
      theme === "dark" ? "github-dark" : "github-light"
    );
    script.setAttribute("crossorigin", "anonymous");
    script.setAttribute("async", "true");

    parent?.appendChild(script);

    return () => {
      while (parent?.firstChild) {
        parent?.removeChild(parent?.lastChild);
      }
    };
  }, [parentRef, theme]);

  return (
    <>
      <div ref={parentRef} />
    </>
  );
};

export default Comments;

Finally as a bonus we can improve the loading speed of the script by preloading it. All we need to do is add a <link> tag the <head> tag with rel="preload".

In Next.js we can do this with the next/head component. If you’re not using Next.js, you can use something like React Helmet.

Comments.tsx

import Head from "next/head";
import type { FC } from "react";
import { useRef, useEffect } from "react";
import { useTheme } from "next-themes";

const Comments: FC = () => {
  const parentRef = useRef<HTMLDivElement>(null);
  const { theme } = useTheme();

  useEffect(() => {
    const parent = parentRef?.current;
    const script = document.createElement("script");

    script.setAttribute("src", "https://utteranc.es/client.js");
    script.setAttribute("repo", "akhila-ariyachandra/akhilaariyachandra.com");
    script.setAttribute("issue-term", "pathname");
    script.setAttribute(
      "theme",
      theme === "dark" ? "github-dark" : "github-light"
    );
    script.setAttribute("crossorigin", "anonymous");
    script.setAttribute("async", "true");

    parent?.appendChild(script);

    return () => {
      while (parent?.firstChild) {
        parent?.removeChild(parent?.lastChild);
      }
    };
  }, [parentRef, theme]);

  return (
    <>
      <Head>
        <link rel="preload" href="https://utteranc.es/client.js" as="script" />
      </Head>

      <div ref={parentRef} />
    </>
  );
};

export default Comments;

You can check how I’ve implemented it my blog here.

If you enjoyed this post please leave a like or two below!!!