reactjslexicaljs

Is it OK to un-subscribe and re-subscribe all my @Lexical/react command listeners on every render?


In @lexical/react, is there a substantial penalty (performance or other) for registering editor commands in a useEffect with no dependency array?

useEffect(() => {
  const unsubscribe = editor.registerCommand(
    KEY_ENTER_COMMAND,
    event => { /* ... */ },
    COMMAND_PRIORITY_HIGH
  )

  return unsubscribe
})

Is this internally demanding for Lexical, or is it merely a question of calling an extra simple function? Or are there maybe some other downsides to this approach?


Solution

  • It's fairly cheap, behind the scenes we just add/delete from a Map and a Set. But it's cheaper if you have to do none of this.

    useCommandSubscription is an OK abstraction, some (untested) code:

    function useCommandSubscription<T>(command: LexicalCommand<T>, fn: CommandListener<T>, priority: CommandListenerPriority): void {
      const [editor] = useLexicalComposerContext();
      useLayoutEffect(() => {
        return editor.registerCommand(command, fn, priority);
      }, [editor]);
    }
    useCommandSubscription(FOO_COMMAND, () => { ... }, priority);
    

    But note how you can further optimize what we provide out of the box:

    useEffect(() => {
      // You can return immediately, no need to store the cleanup function in a variable
      return editor.registerCommand(...);
    }, [editor]);
    

    A common use case is that you listen to multiple commands/updates at once, you can leverage mergeRegister (from @lexical/utils):

    useEffect(() => {
      return mergeRegister(
        editor.registerCommand(..),
        editor.registerCommand(..),
        editor.registerUpdateListener(..),
    }, [editor]);
    

    Side note: Beware when listening to the key enter command. Android works with composition and will not trigger a key enter event. Depending on your use case you may want to explore INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, transforms based on LineBreakNode or ParagraphNode or mutation listeners based on these two nodes.