highlight.js: Syntax highlighting fails in React applications when using the new highlightAll() method
Describe the issue I am trying to use highlightjs on a React project, which uses Next.js (although I don’t think the issue is related). I have added highlight.js using npm (v106.0) and loaded in a style sheet for a theme.
I have a simple function component set up like this:
import hljs from "highlight.js";
function Post(props) {
useEffect(() => {
hljs.highlightAll();
}, []);
return (
<>
<pre>
<code> /* code blocks that i want to highlight */ </code>
</pre>
</>
);
}
This is using the new highlightAll() method call, but when the component is loaded the highlighting fails to take effect, and the <pre><code> block is left unstyled.
Expected behavior I would expect the code block to be styled and the language highlighted using highlightjs and the stylesheet I have selected.
Which language seems to have the issue? The error is not language specific, but all code blocks are failing to get instantiated and highlighted, and any styling applied what so ever.
What I think the issue is
I think the issue is that the new highlightAll() is not compatible with React and the way that React shadow loads components. Line #772 makes a check if the dom has loaded, else it breaks out:
function highlightAll() {
// if we are called too early in the loading process
if (!domLoaded) { wantsHighlight = true; return; }
const blocks = document.querySelectorAll('pre code');
blocks.forEach(highlightElement);
}
The only place that domLoaded is set to true is in the boot() method:
function boot() {
domLoaded = true;
// if a highlight was requested before DOM was loaded, do now
if (wantsHighlight) highlightAll();
}
The problem is that the only place boot() is called from is an event listener:
if (typeof window !== 'undefined' && window.addEventListener) {
window.addEventListener('DOMContentLoaded', boot, false);
}
Since React will not cause the fire of the DomContentLoaded event when a user navigates to a new page, boot() is never called to set this variable to true, and when I am calling highlightAll() from my code it’s is exiting too early. The boot() is also not exposed to be able to call directly from my code.
Workaround
For now, the solution is to call hljs.initHighlighting() (Line 747) since that method does not make the domLoaded check.
But, this method is marked as deprecated, so a fix needs to be put in place to allow this highlighter to continue to work with React applications. Perhaps allow a true parameter to be passed to force it to run the syntax highter code.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 32 (19 by maintainers)
Did you try swapping out the useEffect for the useLayoutEffect hook? It causes the side effect to happen synchronously after DOM manipulation has finished. This is has fixed some issues for me in this situation. Its analygous to firing in the class-component componentDidMount and componentDidUpdate life cycles.
https://reactjs.org/docs/hooks-reference.html#uselayouteffect
I don’t think it would. The problem is it’s not guaranteed that the DOMContentLoaded event is fired when HighLight.js is being loaded. So defacto domLoaded would always be
false.Taking the code from #3039, I would further adapt it to this, to make sure it works as expected even if the DOMContentLoaded event has been fired before Highlight.js was loaded.
I would adapt the code to remove the domLoaded variable altogether and only check the document readystate in the highlight all function.
I think this would work as expected and also simplify the changed code from #3039.
Would it be possible for me to propose my changes on the PR?
Thanks for trying to help! Knowledgeable people on other ecosystems is always good to have.
@jammykam From everything said here so far I’m confused that the patch above wouldn’t resolve your issue. Can you try #3039 and if that doesn’t help then a small isolated example case would still be appreciated. 😃
I’ll take your word for it… I was just assuming if someone COULD do it while we’re solving this I was curious what that might look like and see if we could solve that as well. I wasn’t trying to suggest “best practice” or any such thing for Next.js. If my guess is correct about
highlightAll()not being the correct API in that case anyways - then the problem has solved itself.I’m a big fan of server-side rendering (from the Ruby on Rails world). 😃 For me personally I’d prefer to ship simple HTML pages as “cooked” as possible to the client - for the fastest load times. You may be right though in that perhaps SSG with React/Next.js may not be the best tool for accomplishing such things.
A tiny Github repo that I could build that reproduces just the React/SSG portion of the issue would be great.
I wonder what
document.readyStateis when HLJS is loaded and why that didn’t fix things? I’m still confused whether you’re running too early, or too late, or a combination of BOTH.