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

Viewed 123

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?

1 Answers

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.

Related