javascriptreactjsnext.jsmonaco-editorreact-monaco-editor

Duplicate suggestion in monaco editor react next js


I am using monaco-edtor to create a formula editor in React and Nextjs, I have an edit button to show and hide the editor, the editor only gets shown/mounted only when the user clicks the edit button.

at first, mounting the suggestion is coming as intended, but when the user hides and shows the editor same elements are repeated again as shown in the picture below is there any way to correct this

The photo of the suggestion window.

  1. First-time mounting enter image description here

  2. Second-time mounting enter image description here

  3. Third-time mounting enter image description here

My blue print of my code is like

export const MonacoEditor = () => {
const editorRef = useRef<monacoEditor.editor.IStandaloneCodeEditor | null>(
        null,
    );
    
function HandleEditorDidMount(
        editor: monacoEditor.editor.IStandaloneCodeEditor,
        monaco: typeof monacoEditor,
    ) {
        editorRef.current = editor;

        editorRef.current.onKeyDown((event: monacoEditor.IKeyboardEvent) => {
            if (event.keyCode === monaco.KeyCode.Enter) {
                event.preventDefault();
            }
        });

        // Define your custom language
        monaco.languages.register({
            id: "custom-language",
        });

        editorRef.current.updateOptions({
            lineNumbers: "off",
            lineDecorationsWidth: 1,
            tabCompletion: "off",
            selectionHighlight: false,
            renderLineHighlight: "none",
            minimap: {
                enabled: false,
            },
            overviewRulerLanes: 0,
            scrollbar: {
                horizontal: "hidden",
                handleMouseWheel: true,
            },
            wordWrapColumn: -1,
            wordWrap: "on",
        });
        monaco.languages.registerCompletionItemProvider("custom-language", {
            provideCompletionItems: async (_model, position) => {
                const currentWord = getCurrentWord(editorRef.current, position);

                // Make a request to your backend API to fetch suggestions based on the user's input
                const response = await fetch("http://localhost:8000/fewLabels", {
                    method: "GET",
                });

                const suggestions: Suggestion = await response.json();
                console.log("suggestions ", suggestions);
                const completionItems = Object.keys(suggestions)
                    .filter((keyword) =>
                        keyword.toLowerCase().startsWith(currentWord.toLowerCase()),
                    )
                    .map((keyword) => ({
                        label: keyword,
                        id: keyword,
                        kind: monaco.languages.CompletionItemKind.Keyword,
                        insertText: keyword,
                        range: new monaco.Range(
                            position.lineNumber,
                            position.column - currentWord.length,
                            position.lineNumber,
                            position.column,
                        ),
                    }));

                console.log("completionItems ", completionItems);

                return {
                    suggestions: completionItems,
                };
            },
        });
    }

return(
         <Editor
                height="70px"
                width={"800px"}
                defaultLanguage="custom-language"
                onMount={HandleEditorDidMount}
            />
       )

}

Solution

  • I solved the problem I added a state completionDisposable and seated the function returned by the monaco.languages.registerCompletionItemProvider to it and called as a clean-up function useEffect the modified code is given below

    The modification is followed by the comments // Beginning of Added Segment

    and // Setting the disposable object

    if you understood and works for you don't forget to upvote

    export const MonacoEditor = () => {
        const editorRef = useRef<monacoEditor.editor.IStandaloneCodeEditor | null>(
            null,
        );
    
        // Beginning of Added Segment
        const [completionDisposable, setCompletionDisposable] = useState<monacoEditor.IDisposable>();
    
        useEffect(() => {
            return () => {
                if (
                    completionDisposable?.dispose &&
                    typeof completionDisposable.dispose === "function"
                ) {
                    completionDisposable.dispose();
                }
            };
        }, [completionDisposable]);
    
        // End of added segment
    
    
    
        function HandleEditorDidMount(
            editor: monacoEditor.editor.IStandaloneCodeEditor,
            monaco: typeof monacoEditor,
        ) {
            editorRef.current = editor;
    
            editorRef.current.onKeyDown((event: monacoEditor.IKeyboardEvent) => {
                if (event.keyCode === monaco.KeyCode.Enter) {
                    event.preventDefault();
                }
            });
    
    
            // Define your custom language
            monaco.languages.register({
                id: "custom-language",
            });
    
            editorRef.current.updateOptions({
                lineNumbers: "off",
                lineDecorationsWidth: 1,
                tabCompletion: "off",
                selectionHighlight: false,
                renderLineHighlight: "none",
                minimap: {
                    enabled: false,
                },
                overviewRulerLanes: 0,
                scrollbar: {
                    horizontal: "hidden",
                    handleMouseWheel: true,
                },
                wordWrapColumn: -1,
                wordWrap: "on",
            });
    
            // Setting the disposable object
            setCompletionDisposable(
                monaco.languages.registerCompletionItemProvider("custom-language", {
                    provideCompletionItems: async (_model, position) => {
                        const currentWord = getCurrentWord(editorRef.current, position);
    
                        // Make a request to your backend API to fetch suggestions based on the user's input
                        const response = await fetch("http://localhost:8000/fewLabels", {
                            method: "GET",
                        });
    
                        const suggestions: Suggestion = await response.json();
                        console.log("suggestions ", suggestions);
                        const completionItems = Object.keys(suggestions)
                            .filter((keyword) =>
                                keyword.toLowerCase().startsWith(currentWord.toLowerCase()),
                            )
                            .map((keyword) => ({
                                label: keyword,
                                id: keyword,
                                kind: monaco.languages.CompletionItemKind.Keyword,
                                insertText: keyword,
                                range: new monaco.Range(
                                    position.lineNumber,
                                    position.column - currentWord.length,
                                    position.lineNumber,
                                    position.column,
                                ),
                            }));
    
                        console.log("completionItems ", completionItems);
    
                        return {
                            suggestions: completionItems,
                        };
                    },
                }),
            );
        }
    
    
        return (
            <Editor
                height="70px"
                width={"800px"}
                defaultLanguage="custom-language"
                onMount={HandleEditorDidMount}
            />
        );
    };