AUTHOR
Oscar Salazar, Senior Software Engineer
Oscar is a Software Engineer passionate about frontend development and creative coding, he has worked in several projects involving from video games to rich interactive experiences in different web applications. He loves studying and playing with the newest CSS features to create fantastic art.
Revelant information
The information here was published when CodeMirror 6 was still in beta, since the official release of CoreMirror 6 this blog post was updated. Please refer to this post, to see the latest implementation of CodeMirror 6 in React.
Implementing CodeMirror 6 in React
CodeMirror 6 is a complete rewrite with focus in accessibility and mobile support. Its API is not backwards compatible so we can't use any of the previous libraries.
If you are new to CodeMirror 6 go ahead and read the official documentation. It will guide you through the new modules and teach you how they work together to build a basic editor.
Base editor
First we start by creating a new ref
callback, we will use this ref
to get our DOM element when the component mounts.
const [element, setElement] = useState<HTMLElement>();
const ref = useCallback((node: HTMLElement | null) => {
if (!node) return;
setElement(node);
}, []);
Now that we have a DOM element available we can create our CodeMirror instance.
import { EditorState, EditorView, basicSetup } from "@codemirror/basic-setup";
import { javascript } from "@codemirror/lang-javascript";
useEffect(() => {
if (!element) return;
const view = new EditorView({
state: EditorState.create({
extensions: [basicSetup, javascript()],
}),
parent: element,
});
return () => view.destroy();
}, [element]);
As you can see we are using the package @codemirror/basic-setup
. This is a convenient extension with the base functionality of a common code editor. You can see every extension here.
Let's wrap everything in a hook, add the ability to extend the editor and access it's state.
We could not find the snippet
Make sure you used the correct snippet identifier
We can use this hook anywhere in out application, We are going to create a reusable component.
import useCodeMirror from "./useCodeMirror";
type CodeMirrorProps = {
extensions: Extension[];
};
const CodeMirror = ({ extensions }: CodeMirrorProps) => {
const { ref } = useCodeMirror(extensions);
return <div ref={ref} />;
};
export default CodeMirror;
Extensions and async autocomplete
Extensions in CodeMirror let you change and access the state of the editor internals. You can add widgets, tooltips, lint messages, highlight, get the code, etc.
We are going to use the autocomplete extension to suggest code snippets. To provide completions we are going to need a completion source. This function receives a completion context.
Completion context give us information about the current state of the editor. We can use the methods tokenBefore
and matchBefore
to make suggestions based in the content of the editor.
Let's create our completion source.
import {
Completion,
CompletionContext,
CompletionResult,
} from "@codemirror/autocomplete";
export default async function completionSource(
context: CompletionContext
): Promise<CompletionResult> {
// match everything behind the editor cursor position
const word = context.matchBefore(/.*/);
// continue with a completion only if there is actual text
if (word.from == word.to || word.text.trim().length <= 0) return null;
// implement your data fetching
const options: Completion = await fetchOptions(word.text.trim());
return {
from: word.from,
options,
filter: false,
};
}
The previous completion source function will fetch an API to bring code suggestions. Once an option is selected CodeMirror will insert it at the from
position in the editor.
By default the autocomplete extension use the context to filter the provided options. We disabled this functionality by returning filter false since we always want our options visible.
Now let's create our autocomplete extension.
import {
autocompletion,
closeCompletion,
startCompletion,
} from "@codemirror/autocomplete";
import completionSource from "./completionSource";
import { debounce } from "lodash";
const debouncedStartCompletion = debounce((view) => {
startCompletion(view);
}, 300);
function customCompletionDisplay() {
return EditorView.updateListener.of(({ view, docChanged }) => {
if (docChanged) {
// when a completion is active each keystroke triggers the
// completion source function, to avoid it we close any open
// completion inmediatly.
closeCompletion(view);
debouncedStartCompletion(view);
}
});
}
const extensions = [
autocompletion({
activateOnTyping: false,
override: [completionSource],
}),
customCompletionDisplay(),
];
export default autcomplete;
We need to debounce the start of a completion since most of the time we don't want to call our API with each keystroke. Thats what customCompletionDisplay
will do for us.
Conclusion
CodeMirror 6 is a fantastic web code editor with touchscreen support. It's API is stable enough to use in mid-sized projects and very well documented.
You can get the useCodeMirror hook and many more useful React snippets at Codiga.