monaco-react icon indicating copy to clipboard operation
monaco-react copied to clipboard

Make the onMount function can have a destroy function

Open dingchaoyan1983 opened this issue 3 years ago • 2 comments

currently if we want to initialize some provider, because the provider return a dispose object, when the editor component destroy, we can't add a destroy function to do that just like this:

const editorDidMount: OnMount = (editor, monaco) => {
    editorRef.current = editor;
    monaco.languages.register({ id: 'betalpha' });
    const tokenProvider = monaco.languages.setTokensProvider(
      'betalpha',
      new TokensProviders(),
    );
    const suggestions = transSug(['代码提示']);
    if(suggestions.length){
      const completionItemProvider = monaco.languages.registerCompletionItemProvider(
        'betalpha',
        {
          provideCompletionItems(model, position) {
            position.clone();
            const word = model.getWordAtPosition(position);
            const defaultReplaceRange = word ? new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : monaco.Range.fromPositions(position)
            const defaultRange = { replace: defaultReplaceRange, insert: defaultReplaceRange.setEndPosition(position.lineNumber, position.column) }
            return {
              suggestions: suggestions.map((item) => ({
                range: defaultRange,
                ...item,

                kind: item.icon
                  ? monaco.languages.CompletionItemKind.Variable // 图标
                  : monaco.languages.CompletionItemKind.File,
              })),
            };
          },
        },
      );
    }
  };

we can't destory completionItemProvider and tokenProvider

so if we can have a destroy function that we can write it like this

const editorDidMount: OnMount = (editor, monaco) => {
    editorRef.current = editor;
    monaco.languages.register({ id: 'betalpha' });
    const tokenProvider = monaco.languages.setTokensProvider(
      'betalpha',
      new TokensProviders(),
    );
    const suggestions = transSug(['代码提示']);
   let completionItemProvider;
    if(suggestions.length){
      completionItemProvider = monaco.languages.registerCompletionItemProvider(
        'betalpha',
        {
          provideCompletionItems(model, position) {
            position.clone();
            const word = model.getWordAtPosition(position);
            const defaultReplaceRange = word ? new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : monaco.Range.fromPositions(position)
            const defaultRange = { replace: defaultReplaceRange, insert: defaultReplaceRange.setEndPosition(position.lineNumber, position.column) }
            return {
              suggestions: suggestions.map((item) => ({
                range: defaultRange,
                ...item,

                kind: item.icon
                  ? monaco.languages.CompletionItemKind.Variable // 图标
                  : monaco.languages.CompletionItemKind.File,
              })),
            };
          },
        },
      );
    }

    return () => {
      tokenProvider.dispose();
      if (completionItemProvider) {
         completionItemProvider.dispose()
      }
    }
  };

dingchaoyan1983 avatar Aug 18 '22 01:08 dingchaoyan1983

@dingchaoyan1983 I am not sure if your suggested pattern is suitable for this particular case, especially if your issue has multiple options to be solved:

option 1) use useEffect to handle side effects

function YourCoolComponent() {
  const monacoRef = useRef(null);
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    let tokenProvider;
    let completionItemProvider;

    if (isReady) {
      tokenProvider = monaco.languages.setTokensProvider(...);
      completionItemProvider = monaco.languages.registerCompletionItemProvider(...);
    }
    
    return () => {
      tokenProvider?. dispose();
      completionItemProvider?.dispose();
    };
  }, [isReady]);

  function handleEditorDidMount(editor, monaco) {
    monaco.current = monaco;
    
    setIsReady(true);
  }

  return <Editor onMount={handleEditorDidMount} ... />;
}

option 2) use useMonaco to get monaco instance

import { useMonaco } from '@monaco-editor/react';

...

function YourCoolComponent() {
  const monaco = useMonaco();

  useEffect(() => {
    let tokenProvider;
    let completionItemProvider;

    if (monaco) {
      tokenProvider = monaco.languages.setTokensProvider(...);
      completionItemProvider = monaco.languages.registerCompletionItemProvider(...);
    }
    
    return () => {
      tokenProvider?. dispose();
      completionItemProvider?.dispose();
    };
  }, [monaco]);

  return <Editor ... />;
}

option 3) use useRef to store tokenProvider and completionItemProvider

function YourCoolComponent() {
  const tokenProviderRef = useRef(null);
  const completionItemProviderRef = useRef(null);

  useEffect(() => {    
    return () => {
      tokenProviderRef.current?. dispose();
      completionItemProviderRef.current.?.dispose();
    };
  }, []);

  function handleEditorDidMount(editor, monaco) {
      tokenProviderRef.current = monaco.languages.setTokensProvider(...);
      completionItemProviderRef.current = monaco.languages.registerCompletionItemProvider(...);
  }

  return <Editor onMount={handleEditorDidMount} ... />;
}

suren-atoyan avatar Aug 18 '22 10:08 suren-atoyan

@suren-atoyan yeah, your suggestion is very good, they can resolve my problem also, but it is not intuitive, for options 1, I SHOULD to know the ready variable is indicate that the editor is mounted, so I NEED to know more details for this component, for option 2, does the monaco editor is mounted when monaco variable is ready?, for option 3, I should keep the references

That is all, for the component lifecycle, we just have onMount lifecycle, but we don't expose a lifecycle just like unMount, we should expose this lifecycle to developers, that they can do some cleanup work and they can also do not use this lifecycle just don't return a cleanup function just like this code in react code

useEffect(() => {
// run effect

  return () => {
     //dispose the effect
  }
});

dingchaoyan1983 avatar Aug 19 '22 01:08 dingchaoyan1983

I close this due to inactivity. Feel free to re-open it.

suren-atoyan avatar Jan 19 '24 17:01 suren-atoyan