facebook / lexical

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

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

Open dnamme opened 3 days ago

dnamme commented 3 days 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 3 days 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.

dnamme commented 9 hours 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