Adding utterances comments to your React Blog
Tags: webdev, react
Published: 2022-02-28 04:21:15

> **Originally posted in my [blog](https://akhilaariyachandra.com/blog/utterances-comments-in-react-blog).**

In this post I'll go through how I added comments to my blog using [utterances](https://utteranc.es/), 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](https://github.com/apps/utterances) installed. In my case I have installed it in the repo of my [blog](https://github.com/akhila-ariyachandra/akhilaariyachandra.com).

Next create a component.

```tsx
// 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`.

```tsx
// 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.

```tsx
// 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.

```tsx
// 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](https://github.com/pacocoursey/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.

```tsx
// 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](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload) 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`](https://nextjs.org/docs/api-reference/next/head) component. If you're not using Next.js, you can use something like [React Helmet](https://www.npmjs.com/package/react-helmet).

```tsx
// 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](https://github.com/akhila-ariyachandra/akhilaariyachandra.com/blob/main/components/post/Comments.tsx).