Closed jmau111 closed 5 years ago
Nice, thanks!
Also checkout https://github.com/b6pzeusbc54tvhw5jgpyw8pwz2x6gs/react-utterances (I'm not a react expert, not sure if it's a good example)
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.
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.
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...
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 :
I wrote a guide here too
https://www.vincentntang.com/installing-gatsbyjs-blog-comments/
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;
}
...
}}
/>
);
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.
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?
@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.
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
@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:
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 }, '*')
}
}
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 :)
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 :)
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.
Might be useful. And of course I'm gonna use your tool ! (I use gatsby js)