Fix: CodeMirror Not Working — Editor Not Rendering, Extensions Not Loading, or React State Out of Sync
Quick Answer
How to fix CodeMirror 6 issues — basic setup, language and theme extensions, React integration, vim mode, collaborative editing, custom keybindings, and read-only mode.
The Problem
CodeMirror mounts but shows nothing:
import { EditorView, basicSetup } from 'codemirror';
const view = new EditorView({
parent: document.getElementById('editor')!,
});
// Empty editor — no gutters, no highlightingOr the React wrapper doesn’t sync with state:
const [code, setCode] = useState('hello');
// Editor shows 'hello' but typing doesn't update `code`Or language highlighting doesn’t apply:
JavaScript code renders as plain text — no colorsWhy This Happens
CodeMirror 6 is a modular editor — it ships with almost nothing by default. Every feature is an extension:
basicSetupprovides essential features — without it, you get a plain textarea with no line numbers, no syntax highlighting, and no keyboard shortcuts.basicSetupbundles line numbers, undo/redo, bracket matching, and other fundamentals.- Languages are separate packages —
@codemirror/lang-javascript,@codemirror/lang-python, etc. must be installed and added to the extensions array. Without a language extension, code is plain text. - CodeMirror manages its own state — unlike a controlled React input, CodeMirror maintains internal state. You must use
EditorView.updateListeneror a dispatch handler to sync with React state. Directly settingvaluefrom React doesn’t work without reconciliation. - Themes are extensions, not CSS — CodeMirror 6 uses its own styling system. A theme is an extension that defines editor colors. Without a theme extension, the editor uses minimal browser defaults.
Fix 1: Vanilla JavaScript Setup
npm install codemirror @codemirror/lang-javascript @codemirror/lang-python @codemirror/lang-html @codemirror/lang-css @codemirror/lang-json
npm install @codemirror/theme-one-darkimport { EditorView, basicSetup } from 'codemirror';
import { EditorState } from '@codemirror/state';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
const state = EditorState.create({
doc: 'const greeting = "Hello, World!";\nconsole.log(greeting);',
extensions: [
basicSetup, // Line numbers, undo, brackets, etc.
javascript({ typescript: true, jsx: true }), // Language support
oneDark, // Theme
EditorView.lineWrapping, // Word wrap
EditorView.updateListener.of((update) => {
if (update.docChanged) {
const newCode = update.state.doc.toString();
console.log('Code changed:', newCode);
}
}),
],
});
// Mount — parent element MUST exist and have dimensions
const view = new EditorView({
state,
parent: document.getElementById('editor')!,
});
// Update content programmatically
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: 'new content' },
});
// Cleanup
view.destroy();Fix 2: React Integration
npm install @uiw/react-codemirror
# Or build your own wrapper (see Fix 3)// Using @uiw/react-codemirror (simplest)
'use client';
import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import { useState } from 'react';
function Editor() {
const [code, setCode] = useState('const x: number = 42;');
return (
<CodeMirror
value={code}
height="400px"
theme={oneDark}
extensions={[javascript({ typescript: true, jsx: true })]}
onChange={(value) => setCode(value)}
basicSetup={{
lineNumbers: true,
highlightActiveLineGutter: true,
foldGutter: true,
autocompletion: true,
bracketMatching: true,
closeBrackets: true,
indentOnInput: true,
}}
/>
);
}Fix 3: Custom React Wrapper
// components/CodeEditor.tsx — full control
'use client';
import { useRef, useEffect, useState } from 'react';
import { EditorView, basicSetup } from 'codemirror';
import { EditorState, type Extension } from '@codemirror/state';
import { javascript } from '@codemirror/lang-javascript';
import { python } from '@codemirror/lang-python';
import { html } from '@codemirror/lang-html';
import { css } from '@codemirror/lang-css';
import { json } from '@codemirror/lang-json';
import { oneDark } from '@codemirror/theme-one-dark';
const languageMap: Record<string, () => Extension> = {
javascript: () => javascript({ typescript: false, jsx: true }),
typescript: () => javascript({ typescript: true, jsx: true }),
python: () => python(),
html: () => html(),
css: () => css(),
json: () => json(),
};
interface CodeEditorProps {
value: string;
onChange?: (value: string) => void;
language?: string;
readOnly?: boolean;
height?: string;
}
export function CodeEditor({
value,
onChange,
language = 'typescript',
readOnly = false,
height = '400px',
}: CodeEditorProps) {
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
useEffect(() => {
if (!containerRef.current) return;
const langExtension = languageMap[language]?.() ?? javascript({ typescript: true });
const state = EditorState.create({
doc: value,
extensions: [
basicSetup,
langExtension,
oneDark,
EditorView.lineWrapping,
EditorState.readOnly.of(readOnly),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
onChangeRef.current?.(update.state.doc.toString());
}
}),
],
});
const view = new EditorView({ state, parent: containerRef.current });
viewRef.current = view;
return () => {
view.destroy();
viewRef.current = null;
};
}, [language, readOnly]); // Recreate on language or readOnly change
// Sync external value changes
useEffect(() => {
const view = viewRef.current;
if (!view) return;
const currentValue = view.state.doc.toString();
if (currentValue !== value) {
view.dispatch({
changes: { from: 0, to: currentValue.length, insert: value },
});
}
}, [value]);
return <div ref={containerRef} style={{ height, overflow: 'auto' }} />;
}Fix 4: Custom Extensions
import { keymap } from '@codemirror/view';
import { indentWithTab } from '@codemirror/commands';
import { EditorView } from 'codemirror';
// Tab indentation (not enabled by default)
const tabExtension = keymap.of([indentWithTab]);
// Custom keybindings
const customKeymap = keymap.of([
{
key: 'Mod-s',
run: (view) => {
const code = view.state.doc.toString();
handleSave(code);
return true;
},
},
{
key: 'Mod-Enter',
run: (view) => {
const code = view.state.doc.toString();
handleRun(code);
return true;
},
},
]);
// Read-only mode
import { EditorState } from '@codemirror/state';
const readOnly = EditorState.readOnly.of(true);
// Custom styling
const customTheme = EditorView.theme({
'&': {
fontSize: '14px',
border: '1px solid #333',
borderRadius: '8px',
},
'.cm-content': {
fontFamily: '"Fira Code", monospace',
padding: '12px',
},
'.cm-gutters': {
backgroundColor: '#1a1a2e',
borderRight: '1px solid #333',
},
'.cm-activeLineGutter': {
backgroundColor: '#2a2a4e',
},
'&.cm-focused .cm-cursor': {
borderLeftColor: '#60a5fa',
},
'.cm-selectionBackground': {
backgroundColor: '#3a3a6e !important',
},
});
// Combine extensions
const extensions = [
basicSetup,
javascript({ typescript: true }),
oneDark,
tabExtension,
customKeymap,
customTheme,
EditorView.lineWrapping,
];Fix 5: Autocomplete and Linting
import { autocompletion, CompletionContext } from '@codemirror/autocomplete';
import { linter, type Diagnostic } from '@codemirror/lint';
// Custom autocomplete
function myCompletions(context: CompletionContext) {
const word = context.matchBefore(/\w*/);
if (!word || (word.from === word.to && !context.explicit)) return null;
return {
from: word.from,
options: [
{ label: 'console.log', type: 'function', detail: 'Log to console' },
{ label: 'useState', type: 'function', detail: 'React hook' },
{ label: 'useEffect', type: 'function', detail: 'React hook' },
{ label: 'async', type: 'keyword' },
{ label: 'await', type: 'keyword' },
],
};
}
const completionExtension = autocompletion({ override: [myCompletions] });
// Custom linter
const myLinter = linter((view) => {
const diagnostics: Diagnostic[] = [];
const text = view.state.doc.toString();
// Check for console.log
const regex = /console\.log/g;
let match;
while ((match = regex.exec(text)) !== null) {
diagnostics.push({
from: match.index,
to: match.index + match[0].length,
severity: 'warning',
message: 'Remove console.log before committing',
});
}
return diagnostics;
});
// Add to extensions
const extensions = [basicSetup, completionExtension, myLinter];Fix 6: Dynamic Language Switching
'use client';
import { useState, useRef, useEffect } from 'react';
import { EditorView, basicSetup } from 'codemirror';
import { EditorState, Compartment } from '@codemirror/state';
import { javascript } from '@codemirror/lang-javascript';
import { python } from '@codemirror/lang-python';
import { html } from '@codemirror/lang-html';
function DynamicLanguageEditor() {
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
const langCompartment = useRef(new Compartment());
const [language, setLanguage] = useState('typescript');
useEffect(() => {
if (!containerRef.current) return;
const view = new EditorView({
state: EditorState.create({
doc: '// Start typing...',
extensions: [
basicSetup,
langCompartment.current.of(javascript({ typescript: true })),
],
}),
parent: containerRef.current,
});
viewRef.current = view;
return () => view.destroy();
}, []);
// Switch language without recreating the editor
useEffect(() => {
const view = viewRef.current;
if (!view) return;
const langMap: Record<string, any> = {
typescript: javascript({ typescript: true, jsx: true }),
javascript: javascript({ jsx: true }),
python: python(),
html: html(),
};
view.dispatch({
effects: langCompartment.current.reconfigure(langMap[language] || javascript()),
});
}, [language]);
return (
<div>
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="typescript">TypeScript</option>
<option value="javascript">JavaScript</option>
<option value="python">Python</option>
<option value="html">HTML</option>
</select>
<div ref={containerRef} style={{ height: '400px' }} />
</div>
);
}Still Not Working?
Editor renders but no syntax highlighting — you need a language extension. basicSetup provides editor features but not language support. Install and add @codemirror/lang-javascript (or the appropriate language) to extensions.
React state doesn’t update when typing — CodeMirror manages its own state. Use EditorView.updateListener.of() to listen for changes and update React state. Don’t try to make CodeMirror a controlled component — treat it as uncontrolled with sync.
Editor flickers or recreates on every render — the EditorView should be created once in useEffect, not on every render. Store it in a ref. For dynamic changes (language, theme), use Compartment to reconfigure without recreating.
Tab key inserts focus change instead of indent — basicSetup doesn’t include tab-to-indent to preserve accessibility. Import and add keymap.of([indentWithTab]) from @codemirror/commands to enable tab indentation.
For related editor issues, see Fix: Monaco Editor Not Working and Fix: Tiptap Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: GSAP Not Working — Animations Not Playing, ScrollTrigger Not Firing, or React Cleanup Issues
How to fix GSAP animation issues — timeline and tween basics, ScrollTrigger setup, React useGSAP hook, cleanup and context, SplitText, stagger animations, and Next.js integration.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.
Fix: ky Not Working — Requests Failing, Hooks Not Firing, or Retry Not Working
How to fix ky HTTP client issues — instance creation, hooks (beforeRequest, afterResponse), retry configuration, timeout handling, JSON parsing, error handling, and migration from fetch or axios.
Fix: Mapbox GL JS Not Working — Map Not Rendering, Markers Missing, or Access Token Invalid
How to fix Mapbox GL JS issues — access token setup, React integration with react-map-gl, markers and popups, custom layers, geocoding, directions, and Next.js configuration.