Making react-syntax-highlighter "editable"
react-syntax-highlighter is a great tool for highlighting code snippets, but it is not editable. Let's hack an editable state together!
Jan 13, 2023
•
5 min read
The React library react-syntax-highlighter package is a handy little utility for highlighting code snippets. It supports a wide variety of languages and has a bunch of different themes. While it's very easy to use, it has one major drawback: it's not editable (fair enough since it's a syntax highlighter and not an editor).
I ran into this problem while building a Refraction. While I switched to the obvious solution later (using React-Ace), I thought my hack solution was interesting enough to share.
First up, let's render react-syntax-highlighter
with some static JS code:
const [code] = useState(`
const fib = (n) => {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
};
`);
return (
<SyntaxHighlighter
language="javascript"
style={atomOneDark}
customStyle={{
flex: '1',
background: 'transparent',
}}
>
{code}
</SyntaxHighlighter>
);
Now, let's add a textarea
that is hidden behind the react-syntax-highlighter
component. We'll use the textarea
to capture the user's input.
const [code, setCode] = useState(`
const fib = (n) => {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
};
`);
return (
<div className="relative flex bg-[#282a36]">
<textarea
className="absolute inset-0 resize-none bg-transparent p-2 font-mono text-transparent caret-white outline-none"
value={code}
onChange={(e) => setCode(e.target.value)}
/>
<SyntaxHighlighter
language="javascript"
style={atomOneDark}
customStyle={{
flex: '1',
background: 'transparent',
}}
>
{code}
</SyntaxHighlighter>
</div>
);
Last, we want to focus the textarea
when the user clicks or presses a key on (what looks like) the react-syntax-highlighter
component. We can do this by using a ref
on the textarea
and calling focus()
on it.
Most importantly, we need to make sure the styles of the textarea
are roughly the same as the react-syntax-highlighter
component. Otherwise, the text selection on the textarea
won't look right.
const textareaRef = useRef<HTMLTextAreaElement>(null);
const [code, setCode] = useState(`
const fib = (n) => {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
};
`);
return (
<div
role="button"
tabIndex={0}
onKeyDown={() => textareaRef.current?.focus()}
onClick={() => textareaRef.current?.focus()}
className="relative flex bg-[#282a36]"
>
<textarea
className="absolute inset-0 resize-none bg-transparent p-2 font-mono text-transparent caret-white outline-none"
ref={textareaRef}
value={code}
onChange={(e) => setCode(e.target.value)}
/>
<SyntaxHighlighter
language="javascript"
style={atomOneDark}
customStyle={{
flex: '1',
background: 'transparent',
}}
>
{code}
</SyntaxHighlighter>
</div>
);
Two interesting things to note:
text-transparent
is a TailwindCSS class that makes the text transparent. This is important because we want thetextarea
to be invisible, but we still want the characters to be selectable.caret-white
is a TailwindCSS class that makes the caret white. This is important because thetextarea
is transparent, so the caret would be invisible. We want the caret to be visible so the user knows where they are typing.
And that's it! Now you can "edit" the code snippet in the react-syntax-highlighter
component.