domEventHandlers (blur)

EditorView.domEventHandlers({ blur: handleBlur })
The blur eventhandler doesn’t provide updated state value.

If handleBlur has a state value with it and suppose it consoling the state value.
Then even after the change in the state value, when the editor is focused out the handleBlur provides the same initial value instead of the updated state value.

extensions: [
				EditorView.editorAttributes.of({
					class: classNames('bg-white', editorClass)
				}),
				EditorView.contentAttributes.of({ class: classNames(editorContainerClass) }),
				placeholder('// Write your code here...'),
				EditorView.theme({
					'&': { ...height, border: '1px solid silver' },
					'.cm-scroller': { overflow: 'auto', 'scrollbar-width': 'thin' },
					'.cm-editor': { border: '1px solid black' },
					'.cm-selectionBackground, .cm-editor::selection': {
						'background-color': '#597fd9 !important'
					},
				}),
				editableConf.of(EditorView.editable.of(!readonly)),
				basicSetup,
				tabSizeConf.of(EditorState.tabSize.of(tabSize)),
				indentUnitConf.of(indentUnit.of(getIndentSpace())),
				languageConf.of(selectedLang),
				EditorView.lineWrapping,
				syntaxHighlighting(defaultHighlightStyle),
				keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab, {
					key: 'Enter',
					run: insertNewlineAndIndent
				}]),
				onUpdate,
				EditorView.domEventHandlers({ blur: **handleBlur** }),
			],

function **handleBlur**(): void {
		console.log(state);
	}

Your handleBlur is accessing a global state binding that’s not even defined in the code you show. I’m not sure what you’re expecting to happen there, but using the state property on the view object passed to the handler will probably give you better results.

/**

  • This component allows the user to show and edit the code of html, css, javascript and json.
  • It supports code formatting, indentation, syntaxHighlighting and many more.
    */
    import React, { useRef, useEffect, useState, forwardRef } from ‘react’;
    import classNames from ‘classnames’;
    import { EditorState, type Extension } from ‘@codemirror/state’;
    import { basicSetup } from ‘codemirror’;
    import { EditorView, ViewUpdate, placeholder } from ‘@codemirror/view’;
    import { syntaxHighlighting, defaultHighlightStyle } from ‘@codemirror/language’;

// Interfaces
export interface CodeEditorProps {
value?: string;
onChange?: ((value: string) => void);
onBlur?: ((value: string) => void);
}

export interface CodeEditorRef {
formatCode: () => void;
}

// eslint-disable-next-line max-lines-per-function
function CodeEditorComponent({
value = ‘’,
onChange = undefined,
onBlur = undefined,
}: CodeEditorProps, ref: React.Ref): React.ReactElement {
// States
const [code, setCode] = useState(value);

// Refs
const editorRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
const editorViewRef: React.MutableRefObject<EditorView | null> = useRef(null);

useEffect(() => {
	if (code !== value) {
		onChange?.(code);
	}
}, [code]);

/** Lifecycle method to create the editor on the first render with the provided props data. */
useEffect(() => {
	const onUpdate: Extension = EditorView.updateListener.of((update: ViewUpdate) => {
		const updatedCode = update.state.doc.toString();
		setCode(updatedCode);
	});

	const startState = EditorState.create({
		doc: code,
		extensions: [
			EditorView.editorAttributes.of({
				class: classNames('bg-white')
			}),
			placeholder('// Write your code here...'),
			EditorView.theme({
				'&': { border: '1px solid silver' },
				'.cm-scroller': { overflow: 'auto', 'scrollbar-width': 'thin' },
				'.cm-editor': { border: '1px solid black' },
				'.cm-selectionBackground, .cm-editor::selection': {
					'background-color': '#597fd9 !important'
				},
			}),
			basicSetup,
			EditorView.lineWrapping,
			syntaxHighlighting(defaultHighlightStyle),
			onUpdate,
			// EditorView.domEventHandlers({ blur: handleBlur }),
		],
	});

	const view: EditorView = new EditorView({ state: startState, parent: editorRef.current as Element });
	editorViewRef.current = view;

	view.contentDOM.addEventListener('blur', handleBlur);

	return () => {
		view.destroy();
	};
}, []);

/**
 * Function to dispatch the editor function.
 * This function is created in order to avoid the editor view check on all the functions.
 */
function dispatchEditorFn(fn: (editorView: EditorView) => void): void {
	const editorView: EditorView | null = editorViewRef?.current;
	if (editorView) {
		fn(editorView);
	}
}

function triggerOnBlurCallback(editorView: EditorView): void {
	const codeString: string = editorView.state.doc.toString();
	onBlur?.(codeString);
}

function handleBlur(): void {
	dispatchEditorFn(triggerOnBlurCallback);
}

return (
	<div
		ref={editorRef}
	>
	</div>
);

}
const CodeEditor = forwardRef(CodeEditorComponent);
export default React.memo(CodeEditor);

Here is my component sample code, I provide the onBlur callback so that user can perform some action. I tried add event listener to the contentDOM as well but nothing seems to work.

The way I consume the component.
App.tsx
const [value, setValue] = useState(10);

<Button onClick={() => setValue(20)}>Change state
<CodeEditor onBlur={() => {hitApiWithPayload(value)}; console.log("current value: ", value); />

Steps to reproduce

  1. click on editor and then click outside.
    ==> result: console==> current value 10
  2. Click change state button.
  3. Again click inside the editor and again outside.
    ==> result: console==> current value 10 (wrong result the value should be 20 here)