preact
preact copied to clipboard
PrismJS code is duplicated on 1st re-render
Code highlighted by PrismJS is duplicated after the first re-render
Steps to reproduce the behaviour:
- Go to Codesandbox - Preact
- Click on 'Trigger re-render' button
Expected behaviour Codesandbox - React
Hmm - this is sortof a fluke in both libraries TBH. React wipes out Prism's injected HTML because they use .textContent to assign the text of Elements with a single Text child. We use a different technique that is faster because it doesn't wipe content.
I believe the fix here is to set dangerouslySetInnerHTML={{ }} (empty) on the <code> element. Or, wrap it in a Component and add shouldComponentUpdate(){return false} to prevent diffing (since it's just thrown away when highlightAll() is called anyway):
globalThis.Prism = globalThis.Prism || {};
globalThis.Prism.manual = true;
import Prism from 'prism';
import { useEffect, useRef } from 'preact';
const SKIP_DIFFING = typeof document !== 'undefined' ? {} : undefined;
function Prism({ code, language }) {
const ref = useRef();
useEffect(() => {
ref.current.textContent = code;
Prism.highlightElement(ref.current);
}, [code, language]);
return (
<pre className={'language-'+language'}>
<code ref={ref} className={'language-'+language'} dangerouslySetInnerHTML={SKIP_DIFFING}>
{code}
</code>
</pre>
);
}
// usage:
<Prism code={code} language="js" />
There's also a much nicer way to use Prism with (p)react, if you're interested - they provide a string-to-string API that avoids the DOM stuff entirely. Let me know if you want an example.
Hi Jason, the string-to-string API sounds interesting, an example would be great!
In the meantime, I'll try implementing the changes you've suggested. Thanks!
Use the low level highlight function from the Prism API to convert your code to highlighted code:
const highlightedCode = Prism.highlight(
code,
Prism.languages.javascript,
"javascript"
);
Then display the highlighted code inside your component. The variable highlightedCode now contains the necessary html tags to do the highlighting, and for security reasons Preact automatically escapes html tags inside variables. So you'll need to explicitly opt out of this security mechanism.
Use dangerouslySetInnerHTML to have the content of the variable highlightedCode not escaped.
Do so only if the content comes from a trusted source!
return (
<>
<pre className="language-js">
<code
className="language-js"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
></code>
</pre>
<button onClick={handleClick}>Trigger re-render</button>
<div>Re-renders: {rerenders}</div>
</>
);
Since the code is already hightlighted, you can now get rid of the useEffect call. Adapted version of your original code, using Prism's string-to-string API can be found here: https://codesandbox.io/s/preact-prism-forked-h8lm1?file=/pages/index.js