Search code examples
javascriptcssreactjstransitiondraftjs

draft.js: making editor content multipage


I have a difficult problem to solve.

Here is my sample project

Basically I want to create a editable book-like container and since each book page is another container, I can't figure how to make an editor transition from one page to another, once the page is filled with text.

Here is what I'm trying to achieve: enter image description here


My first thought was to use a shared state for multiple editor instances, but as you can see in the example code, it doesn't work as expected and same text appears in two pages.

How can I achieve a multi-container transition when using draft.js?


Solution

  • The logic in general is to listen to changes of the first editor, count characters / word / lines (or more advanced calculation, if the editor has scroll for example) and finally focus the second editor if needed.

    This answer covers, listen, dummy calculation and focus on the next editor. This answer doesn't cover the calculation itself nor dynamic number of pages nor moving back from second editor when deleting content etc. But I believe that it's a direction to your final goal.

    Listen

    First of all, I moved the state creation and handling back to the Editor component itself. What "interests" the parent component ("App") is to get notified when its content changed.

    But now, App needs to access the ("draft-js") editor in order to call focus. This will solve by creating "ref" in App and propagate it using forwardRef.

    const editorRef = React.useRef();
    
    const Editor = React.forwardRef((props, ref) => {
      return <MyEditor name="editor-2" forwardRef={ref} />;
    });
    
    ...
    
    return (
      ...
      <Editor ref={editorRef} />
    )
    

    and in Editor.js

    function MyEditor({ onChange, forwardRef }) {
      ...
      return (
        <Editor
          ref={forwardRef}
      )
    });
    

    Next thing is to add onChange to Editor so App get notified when the content has been changed.

    <Editor
      ref={forwardRef}
      stripPastedStyles
      editorState={editorState}
      onChange={editorState => {
        setEditorState(editorState);
        onChange && onChange(editorState); // <---
      }}
    />
    

    Handle the change and focus

    The first editor is now

    <MyEditor name="editor-1" onChange={onEditorChange} />
    

    and onEditorChange is

    const onEditorChange = editorState => {
      const text = editorState.getCurrentContent().getPlainText();
      if (text.length >= 5) {
        setTimeout(() => {
          editorRef.current.focus();
        });
      }
    };
    

    Currently it checks if the content's length is 5. You probably want to calculate it in a more sophisticated way.

    The last question is, why setTimeout? Well, the simple answer is that without it, "draft-js" throw an error. I believe that it's something with their implementation of release / delete / clear global variables or something.

    And the most important part, the code and live demo :)

    https://codesandbox.io/s/young-shape-t6kcc?file=/src/App.js