facebook / lexical

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

Bug: After update lexical version, API request in `INSERT_IMAGE_COMMAND` causes an error. #3730

Closed fomachanyade closed 1 year ago

fomachanyade commented 1 year ago

description

Thank you for contributors in advance.

My team adapt Lexical for collaborative text edit.

Now, I am trying to update lexical version from 0.3.5 to 0.7.6 (current latest), and faced a problem(rest is fine!).

The editor has a feature that uses can paste an image. Inside editor, INSERT_IMAGE_COMMAND invoked and fires API request that uploads the image to S3 and return url.

This feature worked fine, but after update Lexical version to the latest, It causes below error only at staging environment on Heroku (where NODE_ENV is production).

ReferenceError: React is not defined
at m5r (LexicalErrorBoundary.prod.js:10:264)
at LF (react-dom.production.min.js:157:137)
at Jne (react-dom.production.min.js:267:460)
at Kne (react-dom.production.min.js:250:347)
at iUe (react-dom.production.min.js:250:278)
at TT (react-dom.production.min.js:250:138)
at u8 (react-dom.production.min.js:243:163)
at react-dom.production.min.js:123:115
at e.unstable_runWithPriority (scheduler.production.min.js:18:343)
at Rg (react-dom.production.min.js:122:325)

After spending several hours for debugging, I found that this error causes at API request point, but its response is 201 and OK(confirmed at dev tool network tab).

My Image_plugin.tsx imported React, so I have no idea about what happened.

Any Advises are welcomed.

Below is small reproduction.

editor.tsx

const Editor = memo<EditorProps>((props) => {

  const [uploadImage] = useUploadImageFromEditorMutation();

  const onUploadImage = async (src: string): Promise<UploadedFileType> => {
    try {
            ....
            // Where causes an error.
      const result = await uploadImage({
        variables: { input: { projectId, imageUrl: src } }
      });
            ....
      // return UploadedFile date;
    } catch (e) {
      // error handling
    }
  };

 'type !== 'image/jpeg' &&

  const placeholderText = getPlaceholderText(loading, readonly, placeholder);

  return (
    <div style={{ position: 'relative' }}>

      <div className="sgmsEditor__container">
        <LexicalComposer initialConfig={{ namespace: namespace, editorState: initialEditorState, ...initialConfig }}>
          <>
            {/* <HistoryPlugin /> */}

            <RichTextPlugin
              contentEditable={
                <ContentEditable className={`custome_class_name`} />
              }
              placeholder={<div className="custome_class_name">{placeholderText}</div>}
              ErrorBoundary={LexicalErrorBoundary}
            />
              /*
                            plungins are listed here.
                        */
            <ImagePlugin onUploadImage={onUploadImage.bind(this)} onPaste={onPaste.bind(this)} />
            {children}
            {sync && !offline && (
              <>
                <CollaborationPlugin
                  id={editorId}
                  providerFactory={createWebsocketProvider}
                  shouldBootstrap={false}
                  username={username}
                />
              </>
            )}
          </>
        </LexicalComposer>
      </div>
    </div>
  );
});

export default Editor;

image_plugin.tsx

import React, { FunctionComponent, useEffect } from 'react';
import {
  LexicalCommand,
  LexicalEditor,
  COMMAND_PRIORITY_NORMAL,
  PASTE_COMMAND,
  $getSelection,
  $isRangeSelection,
  $isNodeSelection,
  $isRootNode,
  COMMAND_PRIORITY_EDITOR,
  createCommand
} from 'lexical';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $insertDataTransferForRichText } from '@lexical/clipboard';
import { mergeRegister } from '@lexical/utils';
import { $createImageNode, ImageNode, ImagePayload } from '../nodes/image_node';
import { UpdateImageCommand, UploadedFileType } from '../types';

export type InsertImagePayload = Readonly<ImagePayload>;

type UpdateImageNode = {
  node: ImageNode;
  resourceId: string;
  src: string;
  originalSrc: string;
};

export type ImagePastePluginProps = {
  onUploadImage: (src: string) => Promise<UploadedFileType>;
  onPaste: (e: ClipboardEvent, editor: LexicalEditor) => boolean;
};

export const INSERT_IMAGE_COMMAND: LexicalCommand<InsertImagePayload> = createCommand();
export const UPDATE_IMAGE_COMMAND: LexicalCommand<UpdateImageCommand> = createCommand();
export const ATTACH_IMAGE_COMMAND: LexicalCommand<DataTransfer> = createCommand();

const ImagePastePlugin: FunctionComponent<ImagePastePluginProps> = (props) => {
  const { onUploadImage, onPaste } = props;

  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    return mergeRegister(
      editor.registerCommand(
        INSERT_IMAGE_COMMAND,
        (payload: InsertImagePayload) => {
          const selection = $getSelection();
          if ($isRangeSelection(selection)) {
            if ($isRootNode(selection.anchor.getNode())) {
              selection.insertParagraph();
            }

            const imageNode = $createImageNode({ resourceId: '', src: '', originalSrc: '' });
            selection.insertNodes([imageNode]);
            selection.insertParagraph();

            onUploadImage(payload.src)
              .then(({ resourceId, src, originalSrc }) => {
                editor.dispatchCommand(UPDATE_IMAGE_COMMAND, { node: imageNode, resourceId, src, originalSrc });
              })
              .catch((error) => {
                console.error('inserting Image Error', error);
              });
          }
          return true;
        },
        COMMAND_PRIORITY_EDITOR
      ),
            /*
                registers other commands
            */
      );
  }, [editor]);

  return null;
};

export default ImagePastePlugin;

Lexical version: 0.7.6

Steps To Reproduce

1. 2.

Link to code example:

The current behavior

causes Reference error

The expected behavior

causes no error or show more detailed messages

thegreatercurve commented 1 year ago

I'm not sure what the issue is here, especially with the error message. React is definitely imported in the LexicalErrorBoundary. Can you add a reproducible sandbox?

thegreatercurve commented 1 year ago

Closing for now due to lack of reproducibility. React is definitely imported in the offending component.

fomachanyade commented 1 year ago

After i changed LexicalErrorBoundary to my own one, it worked. If I have spare time this weekend, iI will reproduce failed one and succeeded one.