Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

example for react use #161

Closed
jmau111 opened this issue May 12, 2019 · 12 comments
Closed

example for react use #161

jmau111 opened this issue May 12, 2019 · 12 comments

Comments

@jmau111
Copy link

jmau111 commented May 12, 2019

hi @jdanyow,

Your work is awesome ! Just want to contribute this example made with JSX (react) cause it took me some time especially with the custom attributes for script tag.

import React, {Component} from "react";

export default class Comments extends Component {

  componentDidMount () {
      let script = document.createElement("script");
      let anchor = document.getElementById("inject-comments-for-uterances");
      script.setAttribute("src", "https://utteranc.es/client.js");
      script.setAttribute("crossorigin","anonymous");
      script.setAttribute("async", true);
      script.setAttribute("repo", "[ENTER REPO HERE]");
      script.setAttribute("issue-term", "pathname");
      script.setAttribute( "theme", "github-light");
      anchor.appendChild(script);
  }

  render() {
    return (
        <div id="inject-comments-for-uterances"></div>
    );
  }
}

Might be useful. And of course I'm gonna use your tool ! (I use gatsby js)

@jdanyow
Copy link
Member

jdanyow commented May 12, 2019

Nice, thanks!

Also checkout https://github.com/b6pzeusbc54tvhw5jgpyw8pwz2x6gs/react-utterances
(I'm not a react expert, not sure if it's a good example)

@jdanyow jdanyow closed this as completed May 12, 2019
@jmau111
Copy link
Author

jmau111 commented May 12, 2019

Also checkout https://github.com/b6pzeusbc54tvhw5jgpyw8pwz2x6gs/react-utterances
(I'm not a react expert, not sure if it's a good example)

neither do I, but I guess the important thing here is componentDidMount in the lifescycle and to use setAttribute(). Thanks for the link, seems better with the whole list of parameters.

@osdiab
Copy link

osdiab commented Nov 7, 2019

For a more concise approach to it, this is how I just did it on my repo:

export const UtterancesComments: React.FC = () => (
  <section
    ref={elem => {
      if (!elem) {
        return;
      }
      const scriptElem = document.createElement("script");
      scriptElem.src = "https://utteranc.es/client.js";
      scriptElem.async = true;
      scriptElem.crossOrigin = "anonymous";
      scriptElem.setAttribute("repo", "osdiab/osdiab.github.io");
      scriptElem.setAttribute("issue-term", "pathname");
      scriptElem.setAttribute("label", "blog-comment");
      scriptElem.setAttribute("theme", "github-light");
      elem.appendChild(scriptElem);
    }}
  />
);

This should be safe - you get the DOM elem, this gets invoked once on mount, and once with null on unmount.

@rscharfer
Copy link

I love this, but I am wondering why it works now. It seems on subsequent renders, React would see that it is supposed to render an empty div and then delete the utterances iframe that was rendered in there by the script...

@vincentntang
Copy link

vincentntang commented Nov 21, 2020

I use GatsbyJs (react)

I've run into some troubles installing utteranc.es. What I did wrong was I had included https://github.com/vincentntang/ in the reponame, but this should be omitted when specifying the repo attribute

Here's the Comments component I use for my personal blog site:

import React, {Component} from "react";
export default class Comments extends Component {

  constructor(props){ 
    super(props);
    this.commentBox = React.createRef();
  }
  componentDidMount () {
      let scriptEl = document.createElement("script");
      scriptEl.setAttribute("src", "https://utteranc.es/client.js");
      scriptEl.setAttribute("crossorigin","anonymous");
      scriptEl.setAttribute("async", true);
      scriptEl.setAttribute("repo", "vincentntang/vincentntang.com-comments");
      scriptEl.setAttribute("issue-term", "pathname");
      scriptEl.setAttribute( "theme", "github-light");
      this.commentBox.current.appendChild(scriptEl);
  }

  render() {
    return (
        <div ref={this.commentBox} className="comment-box"></div>
    );
  }
}

I have my comments at

and my blog at

live site is at :

@vincentntang
Copy link

@luizwhite
Copy link

For a more concise approach to it, this is how I just did it on my repo:

export const UtterancesComments: React.FC = () => (
  <section
    ref={elem => {
      if (!elem) {
        return;
      }
      const scriptElem = document.createElement("script");
      scriptElem.src = "https://utteranc.es/client.js";
      scriptElem.async = true;
      scriptElem.crossOrigin = "anonymous";
      scriptElem.setAttribute("repo", "osdiab/osdiab.github.io");
      scriptElem.setAttribute("issue-term", "pathname");
      scriptElem.setAttribute("label", "blog-comment");
      scriptElem.setAttribute("theme", "github-light");
      elem.appendChild(scriptElem);
    }}
  />
);

This should be safe - you get the DOM elem, this gets invoked once on mount, and once with null on unmount.

Nice code, I used this but I had to check if the element already has child nodes, because this code creating a utterances child div every time i navigated to that page
Didn't know though if I was doing something wrong, but i guess not.

export const UtterancesComments: React.FC = () => (
  <section
    ref={(elem) => {
      if (!elem || elem.childNodes.length) {
        return;
      }
    ...
    }}
  />
);

@PsyGik
Copy link

PsyGik commented May 24, 2021

Using a hook:

import { useEffect, useState } from 'react';

export const useUtterances = (commentNodeId) => {
	useEffect(() => {
                 const scriptParentNode = document.getElementById(commentNodeId);
		if (!scriptParentNode) return;
		// docs - https://utteranc.es/
		const script = document.createElement('script');
		script.src = 'https://utteranc.es/client.js';
		script.async = true;
		script.setAttribute('repo', ${YOUR_REPO_NAME});
		script.setAttribute('issue-term', 'pathname');
		script.setAttribute('label', 'comment :speech_balloon:');
		script.setAttribute('theme', 'photon-dark');
		script.setAttribute('crossorigin', 'anonymous');

		
		scriptParentNode.appendChild(script);

		return () => {
			// cleanup - remove the older script with previous theme
			scriptParentNode.removeChild(scriptParentNode.firstChild);
		};
	}, [commentNodeId]);
};

Usage:

import React from 'react';
import { useUtterances } from '../../hooks/useUtterances';

const commentNodeId = 'comments';

const Comments = () => {
	useUtterances(commentNodeId);
	return <div id={commentNodeId} />;
};

export default Comments;

I've written an article which also shows how to lazy load, so that the comments show up when user reaches to the end of the page, (or wherever your comments are on the page), using Intersection Observer API.

@deadcoder0904
Copy link

Typescript using @PsyGik's solution

import React from 'react'

// username/repo format
const REPO_NAME = '<username>/<repo>'

export const useUtterances = (commentNodeId: string) => {
	React.useEffect(() => {
		const scriptParentNode = document.getElementById(commentNodeId)
		if (!scriptParentNode) return

		// docs - https://utteranc.es/
		const script = document.createElement('script')
		script.src = 'https://utteranc.es/client.js'
		script.async = true
		script.setAttribute('repo', REPO_NAME)
		script.setAttribute('issue-term', 'pathname')
		script.setAttribute('label', 'comment :speech_balloon:')
		script.setAttribute('theme', 'photon-dark')
		script.setAttribute('crossorigin', 'anonymous')

		scriptParentNode.appendChild(script)

		return () => {
			// cleanup - remove the older script with previous theme
			scriptParentNode.removeChild(scriptParentNode.firstChild as Node)
		}
	}, [commentNodeId])
}

How do I react to theme change? @PsyGik you mention unmounting & remounting the component, how to do that? Isn't there a better way?

@PsyGik
Copy link

PsyGik commented Jul 3, 2021

@deadcoder0904 you can provide another argument to your useEffect which has theme.

import React from 'react'

// username/repo format
const REPO_NAME = '<username>/<repo>'

export const useUtterances = (commentNodeId: string, theme: string) => {
	React.useEffect(() => {
		const scriptParentNode = document.getElementById(commentNodeId)
		if (!scriptParentNode) return

		// docs - https://utteranc.es/
		const script = document.createElement('script')
		script.src = 'https://utteranc.es/client.js'
		script.async = true
		script.setAttribute('repo', REPO_NAME)
		script.setAttribute('issue-term', 'pathname')
		script.setAttribute('label', 'comment :speech_balloon:')
		script.setAttribute('theme', theme)
		script.setAttribute('crossorigin', 'anonymous')

		scriptParentNode.appendChild(script)

		return () => {
			// cleanup - remove the older script with previous theme
			scriptParentNode.removeChild(scriptParentNode.firstChild as Node)
		}
	}, [commentNodeId, theme])
}

Unfortunately because utterance uses a different css file for each theme, the only clean way to update is to unmount/remount, which the above code will do.

image

However, since us developers don't give up easy ;) , here's one using postMessage to the utterance iFrame.

document.getElementsByClassName("utterances-frame")[0].contentWindow.postMessage({ type: "set-theme", theme:  theme },  "*")

Do note that there is a bit of an ugly color interchange thingy happening when the css is unloaded and new file is loaded

@deadcoder0904
Copy link

@PsyGik Awesome. I tried both & I like the iframe version better. It works snappy. I think the only issue it has is it requests Utterances Theme CSS file on every toggle.

However, I'm getting a TS error on React.useEffect dependencies:

React Hook React.useEffect has a missing dependency: 'utterancesTheme'. Either include it or remove the dependency array.eslintreact-hooks/exhaustive-deps

My code is:

DarkMode.tsx

const toggleTheme = () => {
		const newTheme = resolvedTheme === 'light' ? 'dark' : 'light'
		setTheme(newTheme)

		// for utterances
		const frame = document.getElementsByClassName('utterances-frame')[0] as HTMLIFrameElement
		if (frame.contentWindow) {
			const utterancesTheme = resolvedTheme === 'light' ? 'photon-dark' : 'github-light'
			frame.contentWindow.postMessage({ type: 'set-theme', theme: utterancesTheme }, '*')
		}
	}

useUtterances.ts

import React from 'react'
import { useTheme } from 'next-themes'

// username/repo format
const REPO_NAME = '<username>/<repo>'

export const useUtterances = (commentNodeId: string) => {
	const { theme } = useTheme()
	const utterancesTheme = theme === 'light' ? 'github-light' : 'photon-dark'

	React.useEffect(() => {
		const scriptParentNode = document.getElementById(commentNodeId)
		if (!scriptParentNode) return

		// docs - https://utteranc.es/
		const script = document.createElement('script')
		script.src = 'https://utteranc.es/client.js'
		script.async = true
		script.setAttribute('repo', REPO_NAME)
		script.setAttribute('issue-term', 'pathname')
		script.setAttribute('label', 'comment :speech_balloon:')
		script.setAttribute('theme', utterancesTheme)
		script.setAttribute('crossorigin', 'anonymous')

		scriptParentNode.appendChild(script)

		return () => {
			// cleanup - remove the older script with previous theme
			scriptParentNode.removeChild(scriptParentNode.firstChild as Node)
		}
	}, [commentNodeId])
}

I tried emptying the array as it says in the error but it re-renders the component. Same thing with adding utterancesTheme to the dependency.

Any ideas? This is a warning of ESlint. It works though so thank you :)

@teilorbarcelos
Copy link

teilorbarcelos commented Dec 14, 2021

Definitive solution here, without removeChild and appendChild, only with a replaceChildren, see the component in next:

export function Comments() {

  return (
    <section
      style={{ width: '100%' }}
      ref={
        element => {
          if (!element) {
            return
          }

          const scriptElement = document.createElement('script')
          scriptElement.setAttribute('src', 'https://utteranc.es/client.js')
          scriptElement.setAttribute('repo', 'youruser/repo-name')
          scriptElement.setAttribute('issue-term', 'pathname')
          scriptElement.setAttribute('theme', 'photon-dark')
          scriptElement.setAttribute('crossorigin', 'anonymous')
          scriptElement.setAttribute('async', 'true')
          element.replaceChildren(scriptElement)
        }
      }
    />
  )
}

enjoy :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants