React-sortable-hoc resets state of draftjs

I have some editable template. There should be text editing functionality and some drag and drop. I am using draftjs and react-sortable-hoc.

So I have a TextEditor component

import React, { Component } from 'react'
import Editor, { createEditorStateWithText } from 'draft-js-plugins-editor' // eslint-disable-line import/no-unresolved
import createInlineToolbarPlugin from 'draft-js-inline-toolbar-plugin' // eslint-disable-line import/no-unresolved
import 'draft-js-inline-toolbar-plugin/lib/plugin.css'
// import editorStyles from './editorStyles.css'

export default class TextEditor extends Component {
  editor = null
  inlineToolbarPlugin = createInlineToolbarPlugin()
  InlineToolbar = this.inlineToolbarPlugin.InlineToolbar
  plugins = [this.inlineToolbarPlugin]
  text = 'In this editor a toolbar shows up once you select part of the text …'
  state = {
    editorState: createEditorStateWithText(this.props.text || '')
  }
  focus = () => {
    this.editor.focus()
  }

  handleChange = (editorState) => {
    console.info('handleChange TextEditor editorState', editorState)
    this.setState({editorState})
  }

  render () {
    return (
      <div onClick={this.focus} className={this.props.className}>
        <Editor
          editorState={this.state.editorState}
          onChange={this.handleChange}
          plugins={this.plugins}
          ref={(element) => { this.editor = element }}
        />
        <this.InlineToolbar />
      </div>
    )
  }
}

I have a SectionSwap component for drag and drop

import React, { Component } from 'react'
import {
  SortableContainer,
  SortableElement,
  arrayMove
} from 'react-sortable-hoc'

class SectionSwap extends Component {
  state = {
    items: this.props.children,
    style: this.props.style
  }
  SortableItem = SortableElement(({value}) => {
    return (
      <div style={this.state.style}>
        {value}
      </div>
    )
  })
  SortableList = SortableContainer(({items}) => {
    return (
      <div>
        {items.map((value, index) => (
          <this.SortableItem key={`item-${index}`} index={index} value={value} />
        ))}
      </div>
    )
  })
  onSortEnd = ({oldIndex, newIndex}) => {
    let {items} = this.state

    this.setState({
      items: arrayMove(items, oldIndex, newIndex)
    })
  }
  shouldCancelStart = (e) => {
    for (var i = 0; i < e.path.length; i++) {
      if (e.path[i].className === 'DraftEditor-root' || e.path[i].className === 'draftJsToolbar__button__qi1gf' || e.path[i].className === 'draftJsToolbar__buttonWrapper__1Dmqh' || e.path[i].className === 'draftJsToolbar__toolbar__dNtBH') {
        return true
      }
    }
  }
  render () {
    return (
      <this.SortableList
        items={this.state.items}
        axis="x"
        transitionDuration={500}
        onSortEnd={this.onSortEnd}
        shouldCancelStart={this.shouldCancelStart}
          />
    )
  }
}

export default SectionSwap

And I have a template component

import React, { Component } from 'react'
import TextEditor from '../PopupDesigner/TextEditor'
import { createEditorStateWithText } from 'draft-js-plugins-editor'
import SectionSwap from '../SectionSwap/SectionSwap'

export default class Template1 extends Component {
  componentWillMount = () => Meteor.Loader.loadCss('https://hayk94.gitlab.io/plain-html/PopupTemplate2.css')

  render () {
    return (
      <div className="b2CpopupOverlay" style={{background:'#2a1a1a', width: '100%', height: '100%', position: 'fixed'}}>
      <div className="b2Cpopup">
        <div className="b2CflexContainer">
          <SectionSwap>
            <div className="b2CcontentLeft">
              <div className="b2CcontentLeftImage b2cImage">
                <img src="https://backtocart.co/wp-content/uploads/appContent/PopupTemplates/newImages/1.png" alt="" />
              </div>
            </div>
            <div className="b2CcontentRight">
              <div className="b2CcontentRightText">
                <div className="b2cText">
                  <div className="b2Ctitle">
                    <TextEditor text="SEE OUR GREAT NEW APP" />
                  </div>
                  <hr />
                  <div>
                    <img src="https://backtocart.co/wp-content/uploads/appContent/PopupTemplates/newImages/3.png" alt="" />
                  </div>
                  <div className="b2Ctext">
                    <TextEditor text="10&#37; OFF" />
                  </div>
                </div>
                <a className="b2Cbutton b2cButton"><TextEditor text="SHOP NOW" /></a>
              </div>
            </div>
          </SectionSwap>
          <div className="b2Ccopyright">
            Powered by <img src="https:backtocart.co/wp-content/themes/back/img/landing/Logo.png" alt="" />
          </div>
        </div>
        <div className="close">
          &#10005;
        </div>
      </div>
    </div>
    )
  }
}

So both text editing and drag and drop functionality work, however if I type text and then do drag and drop sorting text is reset to it’s default.

What would be the solution here?

Please help and thanks in advance.

I’ve never used react-sortable-hoc, but I’m guessing what’s happening is that when you drag and drop, it un-mounts and re-mounts the TextEditor component. Therefore the editorState is being reset to the initial text prop.

Maybe you could move the editor state up the tree to a higher level component to manage?

Hi Longmate. Thanks for the reply. So I tried implementing it this way. And got some very strange results

import React, { Component } from 'react'
import TextEditor from '../PopupDesigner/TextEditor'
import { createEditorStateWithText } from 'draft-js-plugins-editor'
import SectionSwap from '../SectionSwap/SectionSwap'

export default class Template1 extends Component {
  componentWillMount = () => Meteor.Loader.loadCss('https://hayk94.gitlab.io/plain-html/PopupTemplate2.css')

  state = {
    text1: 'SEE OUR GREAT NEW APP',
    text2: '10&#37; OFF',
    text3: 'SHOP NOW'
  }
  changeTemplateState = (newState) => {
    this.setState(newState)
  }
  render () {
    console.warn('state', this.state)
    return (
      <div className="b2CpopupOverlay" style={{background:'#2a1a1a', width: '100%', height: '100%', position: 'fixed'}}>
      <div className="b2Cpopup">
        <div className="b2CflexContainer">
          <SectionSwap>
            <div className="b2CcontentLeft">
              <div className="b2CcontentLeftImage b2cImage">
                <img src="https://backtocart.co/wp-content/uploads/appContent/PopupTemplates/newImages/1.png" alt="" />
              </div>
            </div>
            <div className="b2CcontentRight">
              <div className="b2CcontentRightText">
                <div className="b2cText">
                  <div className="b2Ctitle">
                    <TextEditor name="text1" changeTemplateState={this.changeTemplateState} text={this.state.text1} />
                  </div>
                  <hr />
                  <div>
                    <img src="https://backtocart.co/wp-content/uploads/appContent/PopupTemplates/newImages/3.png" alt="" />
                  </div>
                  <div className="b2Ctext">
                    <TextEditor name="text2" changeTemplateState={this.changeTemplateState} text={this.state.text2} />
                  </div>
                </div>
                <a className="b2Cbutton b2cButton"><TextEditor name="text3" changeTemplateState={this.changeTemplateState} text={this.state.text3} /></a>
              </div>
            </div>
          </SectionSwap>
          <div className="b2Ccopyright">
            Powered by <img src="https:backtocart.co/wp-content/themes/back/img/landing/Logo.png" alt="" />
          </div>
        </div>
        <div className="close">
          &#10005;
        </div>
      </div>
    </div>
    )
  }
}

I added the default texts into the template state.
I also declared a method to change the state of the template.

And in the TextEditor I do this

  handleChange = (editorState) => {
    console.info('handleChange TextEditor editorState', editorState)
    this.setState({editorState})
    const newState = {}
    newState[this.props.name] = editorState.getCurrentContent().getPlainText()
    this.props.changeTemplateState(newState)
  }

Now when I type everythings seems to be fine. The template texts in the states are being changed. But when I do a drag and drop a very strange thing happens. The template state is being reset as well. Thus the texts reset again as well. However how can this happen? The template is a parent component of the SectionSwap and the SectionSwap has no access to its parent state, isn’t it?

So I tried is to move the state of the editors inside the Template1.jsx

import React, { Component } from 'react'
import TextEditor from '../PopupDesigner/TextEditor'
import { createEditorStateWithText } from 'draft-js-plugins-editor'
import SectionSwap from '../SectionSwap/SectionSwap'

export default class Template1 extends Component {
  componentWillMount = () => Meteor.Loader.loadCss('https://hayk94.gitlab.io/plain-html/PopupTemplate2.css')

  state = {
    editorState1: createEditorStateWithText('SEE OUR GREAT NEW APP'),
    editorState2: createEditorStateWithText('10&#37; OFF'),
    editorState3: createEditorStateWithText('SHOP NOW')
  }
  changeTemplateState = (newState) => {
    this.setState(newState)
  }
  render () {
    console.warn('state1', this.state.editorState1.getCurrentContent().getPlainText())
    console.warn('state2', this.state.editorState2.getCurrentContent().getPlainText())
    console.warn('state3', this.state.editorState3.getCurrentContent().getPlainText())
    return (
      <div className="b2CpopupOverlay" style={{background:'#2a1a1a', width: '100%', height: '100%', position: 'fixed'}}>
      <div className="b2Cpopup">
        <div className="b2CflexContainer">
          <SectionSwap>
            <div className="b2CcontentLeft">
              <div className="b2CcontentLeftImage b2cImage">
                <img src="https://backtocart.co/wp-content/uploads/appContent/PopupTemplates/newImages/1.png" alt="" />
              </div>
            </div>
            <div className="b2CcontentRight">
              <div className="b2CcontentRightText">
                <div className="b2cText">
                  <div className="b2Ctitle">
                    <TextEditor name="editorState1" changeTemplateState={this.changeTemplateState} editorState={this.state.editorState1} />
                  </div>
                  <hr />
                  <div>
                    <img src="https://backtocart.co/wp-content/uploads/appContent/PopupTemplates/newImages/3.png" alt="" />
                  </div>
                  <div className="b2Ctext">
                    <TextEditor name="editorState2" changeTemplateState={this.changeTemplateState} editorState={this.state.editorState2} />
                  </div>
                </div>
                <a className="b2Cbutton b2cButton"><TextEditor name="editorState3" changeTemplateState={this.changeTemplateState} editorState={this.state.editorState3} /></a>
              </div>
            </div>
          </SectionSwap>
          <div className="b2Ccopyright">
            Powered by <img src="https:backtocart.co/wp-content/themes/back/img/landing/Logo.png" alt="" />
          </div>
        </div>
        <div className="close">
          &#10005;
        </div>
      </div>
    </div>
    )
  }
}

Pass the state as a prop and a method to change the Template state.
Then in the TextEditor

import React, { Component } from 'react'
import Editor, { createEditorStateWithText } from 'draft-js-plugins-editor' // eslint-disable-line import/no-unresolved
import createInlineToolbarPlugin from 'draft-js-inline-toolbar-plugin' // eslint-disable-line import/no-unresolved
import 'draft-js-inline-toolbar-plugin/lib/plugin.css'
// import editorStyles from './editorStyles.css'

export default class TextEditor extends Component {
  editor = null
  inlineToolbarPlugin = createInlineToolbarPlugin()
  InlineToolbar = this.inlineToolbarPlugin.InlineToolbar
  plugins = [this.inlineToolbarPlugin]
  text = 'In this editor a toolbar shows up once you select part of the text …'
  state = {
  }
  focus = () => {
    this.editor.focus()
  }

  handleChange = (editorState) => {
    console.info('handleChange TextEditor editorState', editorState)
    const newState = {}
    newState[this.props.name] = editorState
    this.props.changeTemplateState(newState)
  }

  render () {
    return (
      <div onClick={this.focus} className={this.props.className}>
        <Editor
          editorState={this.props.editorState}
          onChange={this.handleChange}
          plugins={this.plugins}
          ref={(element) => { this.editor = element }}
        />
        <this.InlineToolbar />
      </div>
    )
  }
}

This way the drag still works however the texts are not editable at all.