Search code examples
javascriptreactjscodemirror

ReactJS - Infinite loop, setState onChange method


I'm creating a component using codemirror but seems that something in my code is wrong because when I try to update the state of my main component the applications breaks the browser, my research is pointing to a infinite loop at least I can see that when I'm trying to debug.

I hope someone of you can help me with this issue, maybe was my fault, thanks.

my main component:

import React, { Component, PropTypes } from 'react';
import Editor from '../editor';
import Preview from '../preview';

class Playground extends Component {

  constructor(props) {
    super(props);
    this.state = {
      code: 'Hi I am a component',
      newCode: ''
    };
    this.handleCodeChange = this.handleCodeChange.bind(this);
  }

  handleCodeChange(code) {
  // Everything works fine until this step, when the component wants to update the state, start the infinite loop
    this.setState({ newCode: code });
  }

  loadCode(code) {
    this.refs.editor.setCode(code);
  }

  render() {
    return (
      <div>
        <Editor
          ref='editor'
          codeText={this.state.code}
          onChange={this.handleCodeChange}
        />
        <Preview code={this.state.newCode} />
      </div>
    );
  }
}

Playground.propTypes = {
  component: PropTypes.string
};

export default Playground;

my editor component:

import React, { Component, PropTypes } from 'react';
import style from './style';
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript';

class Editor extends Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  componentDidMount() {
    this.editor = CodeMirror.fromTextArea(this.refs.editor, {
      lineNumbers: this.props.lineNumbers,
      matchBrackets: true,
      mode: 'javascript',
      readOnly: this.props.readOnly,
      smartIndent: false,
      tabSize: this.props.tabSize,
      theme: this.props.theme
    });

    this.editor.on('change', this.handleChange);
  }

  componentDidUpdate() {
    if ( !this.props.readOnly ) {
      this.editor.setValue(this.props.codeText);
    }
  }

  handleChange() {
    if ( !this.props.readOnly && this.props.onChange ) {
      this.props.onChange(this.editor.getValue());
    }
  }

  render() {
    let _className = style.editor;
    return (
      <div className={ _className }>
        <textarea ref="editor" defaultValue={this.props.codeText} />
      </div>
    );
  }
}

Editor.propTypes = {
  className: React.PropTypes.string,
  codeText: PropTypes.string,
  lineNumbers: PropTypes.bool,
  onChange: PropTypes.func,
  readOnly: PropTypes.bool,
  tabSize: PropTypes.number,
  theme: PropTypes.string
};

Editor.defaultProps = {
  className: '',
  lineNumbers: false,
  readOnly: false,
  tabSize: 2,
  theme: 'one-dark'
};

export default Editor;

my preview component

import React, { Component, PropTypes } from 'react';

class Preview extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>{this.props.code}</div>
    );
  }
}

Preview.propTypes = {
  code: PropTypes.string
};

export default Preview;

Let me know if you need more info or something else.

Thanks in advance.

Solution

I removed this snippet from the editor component

componentDidUpdate() {
  if ( !this.props.readOnly ) {
    this.editor.setValue(this.props.codeText);
  }
}

The reason is because this method set a new value to the editor that means update, then the process start again infinitely, this method can be used if you are loading the content of the editor manually, that is not my case.

Thanks Anthony


Solution

  • There is an infinite loop because you call this.handleChange in your editor component when a change occurs in the CodeMirror instance. When you call this.handleChange at this moment, you update the state of your main component.

    Then, when you update the main component state, it calls the componentDidUpdate function of your editor component. You set the value of the CodeMirror instance, which fires the change event, updates the main component state, and so on.

    To avoid this behavior, just make comparisons of codeText in componentDidUpdate and handleChange to update only when it's necessary.