Failing to add CodeMirror 6 (and then Succeeding) | DevLog 004
In the last post I created a React component that intercepted and formatted the arguments for console.log and console.error.
In this post I'll be upgrading from a simple <textarea> to something that is designed to show code.
What Should I Use?
After mulling over a few different options, even considering making something from scratch, I decided that CodeMirror would be the best fit for short code snippets.
Fortunately, CodeMirror v6 is out and it's a fantastic opportunity to try it out.
Creating the Editor Component
The editor component will live in src/components/editor/index.tsx. For now, it will be an empty react component.
import React from "react";
export const Editor = () => {
return <></>;
};We'll also need to install some libraries from npm
npm i @codemirror/view @codemirror/stateCodeMirror needs to be attached to a DOM node, so we'll add it via a ref. We'll also use a <section> for some added semantics. In the world of TypeScript types, I learned that any HTML element that doesn't need more than what the base interface provides, like <section>, should use the HTMLElement type.
import React from "react";
export const Editor = () => {
const editorRef = React.useRef<HTMLElement>(null);
return <section ref="editorRef"/>;
};editorRef will be null until the DOM node is selected, so a useEffect is needed to attach the editor.
React.useEffect(() => {
if(editorRef.current === null) return;
const view = new EditorView({
state: EditorState.create({ doc: "hello" }),
parent: editorRef.current
});
}, [editorRef.current]);Passing the State
Note: this section deviates from the video as the mistakes I made there are not particularly important to go over.
The component that will be using the <Editor> will need to evaluate what is being typed in. In other words, the Editor will need to accept a callback function that it can call to pass the state to the parent.
I tried using EditorState["doc"] and EditorState, however, what worked was returning the EditorView.
type EditorProps = {
setView => (view: EditorView) => void
}I also realised that there was no need to have EditorState declared inside the component, so I moved it out.
import React from "react";
const state = EditorState.create({ doc: "hello" });
export const Editor = () => { /* ... */ };In the editor component setViewcan be called as soon as the view is ready. It's also important to destroy the view when the editor is unmounted.
React.useEffect(() => {
if(editorRef.current === null) return;
const view = new EditorView({
state: EditorState.create({ doc: "hello" }),
parent: editorRef.current
});
setView(view);
return () => {
view.destroy();
setView(null);
};
}, [editorRef.current]);The parent component can be updated to use the <Editor> component as well as to update the code evaluator to use the EditorView.state.doc.
import React from "react";
import { Editor } from "../../../components/editor";
import { EditorView } from "@codemirror/view";
const StringPage = () => {
const [view, setView] = React.useState<EditorView | null>(null);
/* ... */
const evaluateCode = () => {
if (view === null) return;
const code = view.state.doc.toString();
try {
new Function(code)();
} catch (e) {
console.error(e);
}
};
return (
<>
{/* irrelevant components omitted */}
<Editor setView={setView} />
</>
);
};
export default StringPage;TL; DR
I put CodeMirror version 6 into a React component.