Skip to content

Instantly share code, notes, and snippets.

@s-cork
Last active September 15, 2021 07:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save s-cork/e7104bace090702f6acbc3004228f2cb to your computer and use it in GitHub Desktop.
Save s-cork/e7104bace090702f6acbc3004228f2cb to your computer and use it in GitHub Desktop.
Codemirror 6 with react
import React, { useEffect, useMemo, useCallback, useLayoutEffect, useState } from "react";
import { EditorView } from "@codemirror/view";
import { EditorState, Compartment, EditorStateConfig, StateEffect, Extension } from "@codemirror/state";
import { indentUnit } from "@codemirror/language";
/** creates an editor view from an initial state - destroys the view on cleanup */
export function useEditorView(initState: (() => EditorStateConfig) | EditorStateConfig = {}): EditorView {
const view = useMemo(
() => new EditorView({ state: EditorState.create(typeof initState === "function" ? initState() : initState) }),
[]
);
useEffect(() => () => view.destroy(), []);
return view;
}
/** adds an extension to a view and updates the extension anytime a dependency changes */
export function useExtension(view: EditorView, extensionCreator: () => Extension, deps: any[]) {
const compartment = useMemo(() => new Compartment(), []);
const extension = useMemo(extensionCreator, deps);
useEffect(() => {
if (!compartment.get(view.state)) {
view.dispatch({ effects: StateEffect.appendConfig.of(compartment.of(extension)) });
} else {
view.dispatch({ effects: compartment.reconfigure(extension) });
}
}, [view, extension]);
}
// example useage
export function useUpdateIndent(view: EditorView, tabSize: number) {
return useExtension(view, () => [EditorState.tabSize.of(tabSize), indentUnit.of(" ".repeat(tabSize))], [tabSize]);
}
/** returns the EditorView connected to a dom node */
export const CodeMirrorElement: React.FC<{ view: EditorView; autoFocus: boolean; className?: string }> = ({
view,
autoFocus,
className,
}) => {
const [domWrapper, setDomWrapper] = useState<HTMLDivElement | null>(null);
const domWrapperNode = useCallback((node: HTMLDivElement | null) => {
setDomWrapper(node);
}, []);
useLayoutEffect(() => {
domWrapper?.appendChild(view.dom);
autoFocus && view.focus();
return () => {
domWrapper?.firstElementChild && domWrapper?.removeChild(domWrapper.firstElementChild);
};
}, [domWrapper, view]);
return <div ref={domWrapperNode} className={className}></div>;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment