Code embed component in React / Next?
So one of my colleagues came to me with this requirement. He wanted to create a code embed component in the React (NextJS) app that he was working on. Initially, he had used the dangerouslySetInnerHTML attribute to set the inner HTML to the embed code (unsanitized!!). This surely works, but due to the way NextJS navigation works, this would render an empty element if you navigate back and forth. He was actually expecting a fix for this. But as a seasoned developer, you should not be applying fixes over what’s already wrong. You got to fix it the proper way.
My first concern was that using dangerouslySetInnerHTML with arbitrary HTML could lead to vulnerabilities such as XSS. (Of course, this could have been mitigated by sanitizing the HTML). The other issue was the direct arbitrary DOM manipulation - which is a bad practice.
Above all, this approach didn’t work with NextJS navigation, so trying to make it work was the challenge. So I came up with the following. I wanted to post it on my blog hoping this would be useful to someone in the future.
import { useEffect, useRef } from "react";
const CodeEmbedExample = ({ embed_code }) => {
const embedRef = useRef();
useEffect(() => {
if (!embed_code) return;
try {
const range = document.createRange();
const documentFragment = range.createContextualFragment(embed_code);
embedRef.current.append(documentFragment);
} catch (error) {
console.error("Error embedding code:", error);
}
return () => {
if (embedRef.current) {
embedRef.current.innerHTML = "";
}
};
}, [embed_code]);
return <div ref={embedRef}></div>;
};
export default CodeEmbedExample;
Please note the above is a cleaned-up example I created for this blog post, which is untested. This is just to communicate the idea and not intended as a copy-paste solution. I’ve intentionally omitted the HTML sanitization part here to keep it simple.
You could improve the code by adding sanitization through a package like DOMPurify, and useMemo for efficiency. Also, you can add PropTypes for type validations, etc. The key takeaway is the usage of ref and createRange functions.
Hope it helps! Feel free to leave a comment if you have any better ways to do this or share if you think it is useful.