Michal Kurz
Michal Kurz

Reputation: 2094

Why are `$create*Node` and `$is*Node` functions prefixed with dollar, when they don't seem to be Lexical context dependent?

In Lexical source code, alongside with every node class, there are also $create*Node and $is*Node functions being exported - for example:

export function $createTextNode(text?: string = ''): TextNode {
  return new TextNode(text);
}

export function $isTextNode(node: ?LexicalNode): boolean %checks {
  return node instanceof TextNode;
}

The same pattern is explicitely encouraged in documentation.

But why the dollar prefixes? According to documentation, the prefix denotes functions which "leverage the lexical scope":

This is when we decided to leverage the "lexical" scope instead to perform EditorState manipulation, and the $ represents just that.

But judging from the implementation, $create*Node and $is*Node seem to be just regular functions, that can be called from anywhere. So what's going on here?

Upvotes: 1

Views: 214

Answers (1)

zurfyx
zurfyx

Reputation: 32797

For the sake of historical reference: https://lexical.dev/docs/faq#why-does-lexical-use-the--prefix-in-the-name-of-many-of-the-functions

It's an exception.

The reason behind this exception is that node manipulation should only be done inside closures. When you think about what you can do with nodes, you quickly realize that storing a reference outside is dangerous and can lead to subtle errors.

For example:

// DO NOT COPY! THIS IS BAD
let textNode;
editor.update(() => {
  ...
  textNode = $createTextNode();
  paragraph.append(textNode);
});
setTimeout(() => {
  editor.update(() => {
    textNode.setTextContent('foo');
  });
}, 3000);

In the example above, I just appended a textNode first, later I am changing its text to foo.

What's the catch? This will break if textNode is not the latest EditorState one. Let's say that between second 0 and 3000 I typed some characters, textNode is no longer the latest node (keep in mind that nodes are immutable) so when I'm trying to use textNode later I'm modifying the wrong node.

Instead, you want to store key references:

let textNodeKey;
editor.update(() => {
  ...
  const textNode = $createTextNode();
  textNodeKey = textNode.getKey();
  paragraph.append(textNode);
});
setTimeout(() => {
  editor.update(() => {
    const node = $getNodeByKey(textNodeKey);
    if (node !== null) {
      $getNodeByKey(textNodeKey).setTextContent('foo');
    }
  });
}, 3000);

That said, this rule mostly applies to $isXNode, on node creation we often generate a unique node key and mark the node dirty, both of these actions require access to the current or pending EditorState, which you wouldn't have access outside the closures.

Upvotes: 3

Related Questions