draft-js-plugins: Multiple draftjs editors cannot be used on the same page using plugins

Multiple draftjs editors cannot be used on the same page using plugins. All interactions are sent to the last instance on the page.

Clone the repo, install dependencies and change line 77 in /stories/index.js

from

.add('CustomToolbarEditor', () => <CustomToolbarEditor />)

to

.add('CustomToolbarEditor', () => <div><CustomToolbarEditor /><CustomToolbarEditor /></div>)

Now run yarn storybook and open the correct example

image

Test functionality of both editors and you will see the controls misbehave and target the last editor.

screenrecording20190222at3

A similar issue is https://github.com/draft-js-plugins/draft-js-plugins/issues/548

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 4
  • Comments: 19 (2 by maintainers)

Most upvoted comments

Thank you, I have converted it to something similar with useState. With this, each editor get’s its own plugins.

const CustomEditor = () => {
  const [editorState, setEditorState] = useState(() => EditorState.createEmpty());

  const [{ plugins, Toolbar }] = useState(() => {
    const toolbarPlugin = createToolbarPlugin();
    const { Toolbar } = toolbarPlugin;
    const plugins = [toolbarPlugin];
    return {
      plugins,
      Toolbar
    };
  });

  const editorRef = useRef(null);

  return (
    <div>
      <div
        className="editor"
        onClick={() => editorRef.current && editorRef.current.focus()}
      >
        <Editor
          editorState={editorState}
          onChange={(newEditorState: EditorState) => setEditorState(newEditorState)}
          plugins={plugins}
          ref={(editor: any) => (editorRef.current = editor)}
        />
        <Toolbar>
          {// may be use React.Fragment instead of div to improve perfomance after React 16
          (externalProps: any) => (
            <div>
              <BoldButton {...externalProps} />
              <ItalicButton {...externalProps} />
              <UnderlineButton {...externalProps} />
              <CodeButton {...externalProps} />
              <Separator {...externalProps} />
              <UnorderedListButton {...externalProps} />
              <OrderedListButton {...externalProps} />
            </div>
          )}
        </Toolbar>
      </div>
    </div>
  );
};

This is really an issue for me. Any suggestions or workarounds?

@MartinSeeler

Taking from the comments, in the example at

https://github.com/draft-js-plugins/draft-js-plugins/blob/master/stories/static-toolbar/src/App.js

I have changed the code to look like the following to resolve this. Multiple plugins will work with this method

import React, { Component } from 'react';
import Editor, { createEditorStateWithText } from 'draft-js-plugins-editor';
import createToolbarPlugin from 'draft-js-static-toolbar-plugin';
import editorStyles from './editorStyles.css';

export default class SimpleInlineToolbarEditor extends Component {
  constructor(props) {
    super(props);
    const text = 'The toolbar above the editor can be used for formatting text, as in conventional static editors  …';
    const inlineToolbarPlugin = createToolbarPlugin();
    this.PluginComponents = {
      Toolbar: inlineToolbarPlugin.Toolbar
    };
    this.plugins = [inlineToolbarPlugin];
    this.state = {
      editorState: createEditorStateWithText(text)
    };
  }

  onChange = (editorState) => {
    this.setState({
      editorState,
    });
  };

  focus = () => {
    this.editor.focus();
  };

  render() {
    const { Toolbar } = this.PluginComponents;
    return (
      <div>
        <div className={editorStyles.editor} onClick={this.focus}>
          <Editor
            editorState={this.state.editorState}
            onChange={this.onChange}
            plugins={this.plugins}
            ref={(element) => { this.editor = element; }}
          />
          <Toolbar />
        </div>
      </div>
    );
  }
}

Helllo everyone!

@MartinSeeler thank you very much for your solution. I had the same issue and it works. But when I do the same with InlineToolbar instead of StaticToolbar, then I keep getting an error store.getItem(...) is not a function.

My main idea is that InlineToolbar needs something extra from the Editor and can’t get it before mount, but I’m not sure how exactly InlineToolbar works.

Do you have any ideas of how I may try to fix this?

Thank you in advance!

@MartinSeeler solution is great, thanks. I did mine with useMemo and got the same effect.

const plugins = ['mensions', 'linkify', 'hashtags']

const { selectedPlugins, MentionSuggestions } = useMemo(() => {
	return initSelectedPlugins({
		plugins
	})
}, [])

initSelectedPlugins is just a utility function to add plugins from an array of string since I use Editor differently for many purpose.

Thanks again.

Thank you, I have converted it to something similar with useState. With this, each editor get’s its own plugins.

const CustomEditor = () => {
  const [editorState, setEditorState] = useState(() => EditorState.createEmpty());

  const [{ plugins, Toolbar }] = useState(() => {
    const toolbarPlugin = createToolbarPlugin();
    const { Toolbar } = toolbarPlugin;
    const plugins = [toolbarPlugin];
    return {
      plugins,
      Toolbar
    };
  });

  const editorRef = useRef(null);

  return (
    <div>
      <div
        className="editor"
        onClick={() => editorRef.current && editorRef.current.focus()}
      >
        <Editor
          editorState={editorState}
          onChange={(newEditorState: EditorState) => setEditorState(newEditorState)}
          plugins={plugins}
          ref={(editor: any) => (editorRef.current = editor)}
        />
        <Toolbar>
          {// may be use React.Fragment instead of div to improve perfomance after React 16
          (externalProps: any) => (
            <div>
              <BoldButton {...externalProps} />
              <ItalicButton {...externalProps} />
              <UnderlineButton {...externalProps} />
              <CodeButton {...externalProps} />
              <Separator {...externalProps} />
              <UnorderedListButton {...externalProps} />
              <OrderedListButton {...externalProps} />
            </div>
          )}
        </Toolbar>
      </div>
    </div>
  );
};

As seen above here: https://github.com/draft-js-plugins/draft-js-plugins/issues/1244#issuecomment-467751445

For functional component solution, this is the correct way to go about it. Cheers!

Thank you, I have converted it to something similar with useState. With this, each editor get’s its own plugins.

const CustomEditor = () => {
  const [editorState, setEditorState] = useState(() => EditorState.createEmpty());

  const [{ plugins, Toolbar }] = useState(() => {
    const toolbarPlugin = createToolbarPlugin();
    const { Toolbar } = toolbarPlugin;
    const plugins = [toolbarPlugin];
    return {
      plugins,
      Toolbar
    };
  });

  const editorRef = useRef(null);

  return (
    <div>
      <div
        className="editor"
        onClick={() => editorRef.current && editorRef.current.focus()}
      >
        <Editor
          editorState={editorState}
          onChange={(newEditorState: EditorState) => setEditorState(newEditorState)}
          plugins={plugins}
          ref={(editor: any) => (editorRef.current = editor)}
        />
        <Toolbar>
          {// may be use React.Fragment instead of div to improve perfomance after React 16
          (externalProps: any) => (
            <div>
              <BoldButton {...externalProps} />
              <ItalicButton {...externalProps} />
              <UnderlineButton {...externalProps} />
              <CodeButton {...externalProps} />
              <Separator {...externalProps} />
              <UnorderedListButton {...externalProps} />
              <OrderedListButton {...externalProps} />
            </div>
          )}
        </Toolbar>
      </div>
    </div>
  );
};

Document this some how, thanks a lot

this is Godsend! thanks! @MartinSeeler

Helllo everyone!

@MartinSeeler thank you very much for your solution. I had the same issue and it works. But when I do the same with InlineToolbar instead of StaticToolbar, then I keep getting an error store.getItem(...) is not a function.

My main idea is that InlineToolbar needs something extra from the Editor and can’t get it before mount, but I’m not sure how exactly InlineToolbar works.

Do you have any ideas of how I may try to fix this?

Thank you in advance!

If this is still a problem for someone, I had the same issue, and the order of adding components matters for InlineToolbar. It needs to be added below the Editor component.

...
<Editor {...all editor props}}/>
<InlineToolbar/>
...

The order for StaticToolbar does not matter.

Thank you, I have converted it to something similar with useState. With this, each editor get’s its own plugins.

const CustomEditor = () => {
  const [editorState, setEditorState] = useState(() => EditorState.createEmpty());

  const [{ plugins, Toolbar }] = useState(() => {
    const toolbarPlugin = createToolbarPlugin();
    const { Toolbar } = toolbarPlugin;
    const plugins = [toolbarPlugin];
    return {
      plugins,
      Toolbar
    };
  });

  const editorRef = useRef(null);

  return (
    <div>
      <div
        className="editor"
        onClick={() => editorRef.current && editorRef.current.focus()}
      >
        <Editor
          editorState={editorState}
          onChange={(newEditorState: EditorState) => setEditorState(newEditorState)}
          plugins={plugins}
          ref={(editor: any) => (editorRef.current = editor)}
        />
        <Toolbar>
          {// may be use React.Fragment instead of div to improve perfomance after React 16
          (externalProps: any) => (
            <div>
              <BoldButton {...externalProps} />
              <ItalicButton {...externalProps} />
              <UnderlineButton {...externalProps} />
              <CodeButton {...externalProps} />
              <Separator {...externalProps} />
              <UnorderedListButton {...externalProps} />
              <OrderedListButton {...externalProps} />
            </div>
          )}
        </Toolbar>
      </div>
    </div>
  );
};

Man, thanks a lot, been struggling a couple of days with this issue. In my case i’m using the Mentions Plugin. With this solution some styles are getting broken, do u have any idea of what it could be?

Examples

Anyways, i moved on a lot with your solution.

Thanks again!

Thank you, I have converted it to something similar with useState. With this, each editor get’s its own plugins.

const CustomEditor = () => {
  const [editorState, setEditorState] = useState(() => EditorState.createEmpty());

  const [{ plugins, Toolbar }] = useState(() => {
    const toolbarPlugin = createToolbarPlugin();
    const { Toolbar } = toolbarPlugin;
    const plugins = [toolbarPlugin];
    return {
      plugins,
      Toolbar
    };
  });

  const editorRef = useRef(null);

  return (
    <div>
      <div
        className="editor"
        onClick={() => editorRef.current && editorRef.current.focus()}
      >
        <Editor
          editorState={editorState}
          onChange={(newEditorState: EditorState) => setEditorState(newEditorState)}
          plugins={plugins}
          ref={(editor: any) => (editorRef.current = editor)}
        />
        <Toolbar>
          {// may be use React.Fragment instead of div to improve perfomance after React 16
          (externalProps: any) => (
            <div>
              <BoldButton {...externalProps} />
              <ItalicButton {...externalProps} />
              <UnderlineButton {...externalProps} />
              <CodeButton {...externalProps} />
              <Separator {...externalProps} />
              <UnorderedListButton {...externalProps} />
              <OrderedListButton {...externalProps} />
            </div>
          )}
        </Toolbar>
      </div>
    </div>
  );
};

Document this some how, thanks a lot

@ZRCU Did you solve this issue?