React Meteor, reactive prop and inner state

243 views Asked by At

I have a component

import React, { Component } from 'react'
import { EditorState, convertToRaw } from 'draft-js'
import { Editor } from 'react-draft-wysiwyg'
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
import draftToHtml from 'draftjs-to-html'

import toolbarOptions from './JpuriTextEditorOptions'

export default class TextEditor extends Component {
  state = {
    editorState: this.props.editorState
  }

  componentWillReceiveProps = nextProps => {
    console.warn('componentWillReceiveProps')
    console.log('nextProps.editorState', nextProps.editorState)
    console.log('this.props.editorState', this.props.editorState)

    this.setState({editorState: nextProps.editorState})
  }

  onEditorStateChange = editorState => this.setState({editorState})

  onBlur = () => this.props.onBlur({key: this.props.myKey, value: draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))})

  render () {
    return (
      <Editor
        wrapperClassName={this.props.wrapperClassName}
        editorClassName={this.props.editorClassName}
        toolbarClassName={`toolbarAbsoluteTop ${this.props.toolbarClassName}`}

        wrapperStyle={this.props.wrapperStyle}
        editorStyle={this.props.editorStyle}
        toolbarStyle={this.props.toolbarStyle}

        editorState={this.state.editorState}
        onEditorStateChange={this.onEditorStateChange}
        onChange={this.props.onChange}

        toolbarOnFocus
        toolbar={toolbarOptions}

        onFocus={this.props.onFocus}
        onBlur={this.onBlur}
        onTab={this.props.onTab}
      />
    )
  }
}

I pass to it a reactive prop, this.props.editorState Then I set it inside the internal state to handle changes there. And only onBlur I save my changes to the mongo db.

Now there is a problem. Whenever I click on the editor I see componentWillReceiveProps logs a few times. And it happens on every change thus I am not able to use my editor component properly. As it's cursor is being reset to the first letter with every click and change.

I am using this library of draftjs https://github.com/jpuri/react-draft-wysiwyg

EDIT

More specifics for the question. setting the state to this.props.editorState in the constructor or in the componentDidMount is solving the issue for the initial state. But there is still just one problem left. In another component I have undo redo functionality that works directly with the db. Now if I type some text. Blur it my change is saved to the db and I can see the text because of the internal text. However if I click the undo button, the text will be undone from the db, however it will still be visible in the editor because of the internal state. So I will have to refresh to see my action undone. With componentWillReceiveProps this issue is solved, however for some reason componentWillReceiveProps is being called every time the state changes even though the props are not changed. Thus bringing above mentioned issues.

1

There are 1 answers

9
Sagiv b.g On

If you are using the Controlled pattern of this component then you should set the state internally and not set it from the outside via props.
For example, if you set the state on each new prop received then theres no reason to use an internal state.
According to their DOCS it seems like you are following their example of controlled EditorState except you are overriding your state on each new prop.
I think if you will just remove this behavior from componentWillReceiveProps i.e setting the state: this.setState({editorState: nextProps.editorState})
It should work like expected.

This is their example by the way:

import React, { Component } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';


class ControlledEditor extends Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty(),
    };
  }

  onEditorStateChange: Function = (editorState) => {
    this.setState({
      editorState,
    });
  };

  render() {
    const { editorState } = this.state;
    return (
      <Editor
        editorState={editorState}
        wrapperClassName="demo-wrapper"
        editorClassName="demo-editor"
        onEditorStateChange={this.onEditorStateChange}
      />
    )
  }
}

EDIT
A followup to your comment, state initialization is usually made in the constructor.
But when you want to initialize state asynchronously you can't and should not do it in the constructor (nor in componentWillMount) because by the time the async operation will finish the render method will already be invoked.
The best place to do that is componentDidMount or eventHandlers, so in your case the onBlur eventHandler:
import React, { Component } from 'react'; import { EditorState } from 'draft-js'; import { Editor } from 'react-draft-wysiwyg';

class ControlledEditor extends Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty(),
    };
  }

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

  onBlur = (event) => {
    // do async stuff here and update state
  };

  render() {
    const { editorState } = this.state;
    return (
      <Editor
        editorState={editorState}
        wrapperClassName="demo-wrapper"
        editorClassName="demo-editor"
        onEditorStateChange={this.onEditorStateChange}
        onBlur={this.onBlur}
      />
    )
  }
}