TestName
TestName

Reputation: 388

React "this" is always null in functions

I have this class. In all functions, this equals null except render() in Idea class. I tried to repeat solution from React.js: onChange event for contentEditable

    
    class Idea extends React.Component {
    render() {
        return (
            <div id="root"
                 onInput={this.emitChange}
                 onBlur={this.emitChange}
                 contentEditable
                 dangerouslySetInnerHTML={{__html: this.props.html}}>
            </div>
        );
    }

    shouldComponentUpdate(nextProps) {
        return nextProps.html !== ReactDOM.findDOMNode(this).innerHTML;
    }

    componentDidUpdate() {
        if (this.props.html !== ReactDOM.findDOMNode(this).innerHTML) {
            ReactDOM.findDOMNode(this).innerHTML = this.props.html;
        }
    }

    emitChange() {
        var html = ReactDOM.findDOMNode(this).innerHTML;
        if (this.props.onChange && html !== this.lastHtml) {

            this.props.onChange({
                target: {
                    value: html
                }
            });
        }
        this.lastHtml = html;
    }
    }

Map class:

    class Map extends React.Component {
    render() {
        var handleChange = function (event) {
            this.setState({html: event.target.value});
        }.bind(this);

        return (
            <div>
                <Header/>
                <div id="map">
                    {this.props.map.root && (
                        <Idea html={this.props.map.root.title} idea={this.props.map.root} onChange={handleChange}/>
                    )}
                </div>
            </div>
        );
    }
    }

I don't understand why this is always null. Can you help me? Thanks

Upvotes: 1

Views: 838

Answers (4)

luanped
luanped

Reputation: 3198

Rather than using the bind method, as an alternative you could also declare emitChange using the property initializer syntax, essentially becoming this:

_emitChange = () => {
    var html = ReactDOM.findDOMNode(this).innerHTML;
    if (this.props.onChange && html !== this.lastHtml) {

        this.props.onChange({
            target: {
                value: html
            }
        });
    }
    this.lastHtml = html;
};

In this case where you just have 1 method to bind either approach is fine, but in cases where you have more methods, the property initializer syntax might save you some bloat in the constructor

Upvotes: 1

Jefree Sujit
Jefree Sujit

Reputation: 1586

There are lots of issues in your code. First, the this scope is not available in the function beacuse, you havent binded it properly. You can use the constructor to bind this to the function. This binding will just occur just once, which will avoid repetitive bindings for every render incase of inline bindings.

Secondly, (Just for better practice) try maximum to avoid using dangerouslySetInnerHTML, because React will not be aware of these DOM nodes, so will not be able to provide a better performance with diffing. And avoid declaring functions inside render, which will inturn get declared everytime the render is called.

class Idea extends React.Component {
constructor () {
 super();
 
 this.emitChange = (e) => this._emitChange(e);
}

render() {
    return (
        <div id="root"
             onInput={this.emitChange}
             onBlur={this.emitChange}
             contentEditable>
         {this.props.children}
        </div>
    );
}

shouldComponentUpdate(nextProps) {
    return nextProps.html !== ReactDOM.findDOMNode(this).innerHTML;
}

componentDidUpdate() {
    if (this.props.html !== ReactDOM.findDOMNode(this).innerHTML) {
        ReactDOM.findDOMNode(this).innerHTML = this.props.html;
    }
}

_emitChange() {
    var html = ReactDOM.findDOMNode(this).innerHTML;
    if (this.props.onChange && html !== this.lastHtml) {

        this.props.onChange({
            target: {
                value: html
            }
        });
    }
    this.lastHtml = html;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

class Map extends React.Component {
constructor () {
 super();
 
 this.handleChange = (e) => this._handleChange(e);
}
// i dont get why  do you store the event in state, if you dont use it.

_handleChange = function (event) {
    this.setState({html: event.target.value});
}

render() {
    return (
        <div>
            <Header/>
            <div id="map">
                {this.props.map.root && (
                    <Idea idea={this.props.map.root} onChange={this.handleChange}>
                    {this.props.map.root.title}
                    </Idea>
                )}
            </div>
        </div>
    );
}
}

Upvotes: 1

Radu Nemerenco
Radu Nemerenco

Reputation: 676

You have to bind this to your functions that will be triggered in render function. The best way to do it is to add a constructor function for the component (best practice is to add it as the first function).

class Idea extends React.Component {
  constructor(props) {
    super(props);
    this.emitChange = this.emitChange.bind(this);
  }
  ...
}

Upvotes: 3

Vladislav Ihost
Vladislav Ihost

Reputation: 2187

You have several issues in above code snippet.

Firstly, then you write something like onInput={this.emitChange}, then this context will be lost - so use bind operation or use new double colon syntax (In your Babel preset set support it, like https://gist.github.com/islahul/73f99302a721a714c002 ).

Secondly, underlying HTML element could be found by ref operation. Use something like <div ref={elm => (this.componentRootDOM = elm)}></div> and then invoke this.componentRootDOM for React lifecycle functions.

Upvotes: 1

Related Questions