Search code examples
javascriptreactjsdraftjs

Draft js Editor gets slower as the content increases due to having many decorators


So my draft-js editor becomes really slow(hacky) the more content I insert (after about 20 decorator replacements). I am guessing this behavior is due to the decorator which checks the entire editor content using regex and replaces the matches with emoji component every time the state changes. I am also creating entities for each of the matches the regex find, I do this by decorating the component with editor state as a prop. Is there a way to make it faster? Here is my decorator :

       {
            strategy: emojiStrategy,
            component: decorateComponentWithProps(RenderEmoji, {
                getEditorState: this.getEditorState,
                setEditorState: this.onChange
            })
        }

here is my emojiStrategy :

function emojiRegexF(regex, contentBlock, callback, contentState) {
    const text = contentBlock.getText();
    let matchArr, start;

    while ((matchArr = regex.exec(text)) !== null) {
        start = matchArr.index;

        callback(start, start + matchArr[0].length);
    }
}
function emojiStrategy(contentBlock, callback, contentState) {
    emojiRegexF(EMOJI_REGEX, contentBlock, callback, contentState);
}

here is my RenderEmoji component:

const RenderEmoji = props => {
    const contentBlock = props.children[0].props.block;
    const emojiKey = contentBlock.getEntityAt(props.children[0].props.start);
    const emojiShortName = props.decoratedText;
    if (!emojiKey) {
        setEntity(props, emojiShortName);
    }

    return (
        <Emoji emoji={emojiShortName} set="emojione" size={24}>
            {props.children}
        </Emoji>
    );
};

and here is my setEntity function that sets the entity for the match:

function setEntity(props, emojiShortName) {
    const editorState = props.getEditorState();
    const contentstate = editorState.getCurrentContent();
    const contentStateWithEntity = contentstate.createEntity(
        "emoji",
        "IMMUTABLE",
        {
            emojiUnicode: emojiShortName
        }
    );

    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
    const oldSelectionState = editorState.getSelection();

    const selectionState = oldSelectionState.merge({
        focusOffset: props.children[0].props.start + props.decoratedText.length,
        anchorOffset: props.children[0].props.start
    });

    const newContentState = Modifier.applyEntity(
        contentstate,
        selectionState,
        entityKey
    );

    const withBlank = Modifier.replaceText(
        newContentState,
        selectionState,
        emojiShortName + " ",
        null,
        entityKey
    );

    const newEditorState = EditorState.push(
        editorState,
        withBlank,
        "apply-entity"
    );

    props.setEditorState(newEditorState);
}

Any way I can optimize this? Thanks


Solution

  • I'm not sure if this would be the source of any real performance problem but there are two things that seem funny:

    • Matching the emoji decorator by regex, even though you're creating entities for them.
    • Changing the editor state (via setEntity) during the rendering of the decorator. Render functions should be pure.

    I imagine you do this type of processing because emojis might be inserted via copy-paste, or via some kind of native emoji picker. A better way would be to:

    1. Insert entities for emojis with the setEntity logic as part of onChange – before the content is saved and ultimately rendered.
    2. Use a decorator strategy based on entities only, e.g.:
    const emojiStrategy = (contentBlock, callback, contentState) => {
        contentBlock.findEntityRanges(character => {
            const entityKey = character.getEntity();
            return (
                entityKey !== null &&
                contentState.getEntity(entityKey).getType() === 'emoji'
            );
        }, callback);
    };
    

    Then your decorator component won't need to update the editor state during the rendering. You also might not need to use decorateComponentWithProps anymore.


    Now back to performance – the best way for you to know for sure how to improve it is to profile your app. You'll be able to tell exactly what takes time to render during keystrokes, and then track down the issue.