facebook / lexical

Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.
https://lexical.dev
MIT License
19.66k stars 1.67k forks source link

Bug: Error when extending DecoratorNode; Element type is invalid: expected a string #6635

Open devnamme opened 1 month ago

devnamme commented 1 month ago

I'm trying to create a custom image plugin by extending DecoratorNode. Notably, the same code works with Lexical version 0.5.0 (as seen in older online tutorials) but not in the most recent stable version. I'm using React + JavaScript for the project.

Lexical version: 0.17.1

Steps To Reproduce

  1. Create ImageNode with code below
  2. Create ImagePlugin with code below
  3. Add buttons to dispatch INSERT_IMAGE_COMMAND

Image node code:

import { DecoratorNode } from 'lexical';

export class ImageNode extends DecoratorNode {
  __src;

  static getType() {
    return 'image';
  }

  static clone(node) {
    return new ImageNode(node.__src, node.__key);
  }

  constructor(src, key) {
    super(key);
    this.__src = src;
  }

  createDOM() {
    return document.createElement('div');
  }

  exportDOM() {
    const element = document.createElement('img');
    element.setAttribute('src', this.__src);

    return { element };
  }

  static importDOM() {
    return {
      img: (node) => {
        return {
          conversion: convertImageElement,
          priority: 0,
        };
      },
    };
  }

  updateDOM() {
    return false;
  }

  static importJSON(serializedNode) {
    const { src } = serializedNode;
    const node = $createImageNode({ src });
    return node;
  }

  exportJSON() {
    return {
      src: this.__src,
      type: 'image',
      version: 1,
    };
  }

  decorate() {
    return (
      <img
        nodeKey={this.getKey()}
        src={this.__src}
      />
    );
  }
}

export function $createImageNode({ src }) {
  return new ImageNode(src);
}

export function $isImageNode(node) {
  return node instanceof ImageNode;
}

const convertImageElement = (domNode) => {
  if (domNode instanceof HTMLImageElement) {
    const { src } = domNode;
    const node = $createImageNode(src);
    return { node };
  }

  return null;
};

Plugin code (no error):

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import {
  $createParagraphNode,
  $insertNodes,
  $isRootOrShadowRoot,
  COMMAND_PRIORITY_EDITOR,
  createCommand,
} from 'lexical';
import { $createImageNode, ImageNode } from '../../components/nodes/ImageNode';

export default function ImagePlugin() {
  const [editor] = useLexicalComposerContext();

  if (!editor.hasNodes([ImageNode])) {
    throw new Error('Not registered');
  }

  editor.registerCommand(
    INSERT_IMAGE_COMMAND,
    (payload) => {
      const node = $createImageNode(payload);
      $insertNodes([node]);

      return true;
    },
    COMMAND_PRIORITY_EDITOR
  );

  return null;
}

export const INSERT_IMAGE_COMMAND = createCommand('INSERT_IMAGE_COMMAND');

The current behavior

Shows the following error when command is dispatched:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `RichTextPlugin`.
    at createFiberFromTypeAndProps (chunk-3IHV7RO6.js?v=82682a78:20442:23)
    at createFiberFromElement (chunk-3IHV7RO6.js?v=82682a78:20463:23)
    at reconcileSingleElement (chunk-3IHV7RO6.js?v=82682a78:10513:31)
    at reconcileChildFibers2 (chunk-3IHV7RO6.js?v=82682a78:10550:43)
    at updatePortalComponent (chunk-3IHV7RO6.js?v=82682a78:15626:37)
    at beginWork (chunk-3IHV7RO6.js?v=82682a78:15941:22)
    at beginWork$1 (chunk-3IHV7RO6.js?v=82682a78:19753:22)
    at performUnitOfWork (chunk-3IHV7RO6.js?v=82682a78:19198:20)
    at workLoopSync (chunk-3IHV7RO6.js?v=82682a78:19137:13)
    at renderRootSync (chunk-3IHV7RO6.js?v=82682a78:19116:15)

The expected behavior

No error.

Impact of fix

The bug doesn't allow me from extending DecoratorNode at all.

etrepum commented 1 month ago

Hard to say without actually being able to run any of this code but I think your project might be misconfigured so it's not compiling jsx correctly for your react version. Try updating to the latest versions of react and whatever bundlers/compilers you're using. If you provide a runnable version of the broken code, e.g. on stackblitz or codesandbox, I can take a closer look.

devnamme commented 1 month ago

Hi @etrepum, here's a copy of my code. I'm using the following lexical packages:

As mentioned, the code doesn't work in 0.17.1 but when I downgrade to version 0.5.0, it works. Thanks!

Repo: https://github.com/dnamme/lexical-test-091924