Reputation: 388
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
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
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
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
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