Steverino Steverino - 1 month ago 21
React JSX Question

Manual innerHTML modification on DOM halts ReactJS listeners

I'm learning ReactJS and the Node/Express ecosystem (early days for me). I have a rudimentary ReactJS file including component definitions and rendering calls. It works as expected on its own. For quick/easy debugging purposes, yesterday I made the following changes in the client code:

// Added HTML id to body tag, no other changes whatsoever to DOM/HTML
<body id='body'>...</body>

// In client code, added:
document.getElementById('body').innerHTML += xhr.responseText;


xhr
is a validated, functional xmlHttpRequest(). I make the request, get a response, and it renders to the body as expected. However, this stops all ReactJS components from listening to their buttons and firing their handlers as defined. There is no console feedback or other sign that anything is wrong, ReactJS just does a first render as expected, then silently stops responding.

If I comment out the single line
document.getEle...
then everything starts working again, including React and the
xhr
itself.

I know in ReactJS the paradigm is not to modify the DOM in this way, but I fail to see why this one line would break all ReactJS functionality. For context, here is a portion of my code:

This component appears to be fine with or without document.getEle... commented out.

// Hello World component: manage cookies and display a simple prop
var HelloWorldComponent = React.createClass({

componentWillMount: function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if( xhr.readyState == 4 && xhr.status == 200 ) {
// NOTE: this `console.log` gives expected result regardless
console.log('Performed initial cookie check. Got response: ' + xhr.responseText);
// document.getElementById('body').innerHTML += '<div>'+xhr.responseText+'</div>';
}
else {
console.log('Tried initial cookie check. Got HTTP response status: ' + xhr.status);
}
}
xhr.open('POST', '/cookieCheck');
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.send();
},

render: function() {
return (
<h1 id='italic-id' className='red-class'>Hello, {this.props.name}!</h1>
);
}
});


This component breaks unless
document.getEle...
is commented out, other wise it works perfectly.


// State component to display simple state
var StateComponent = React.createClass({

// ReactJS Event: this fails with `document.getEle...` appearing elsewhere in the code
incrementCount: function() {
this.setState({
count: this.state.count + 1
});
},

getInitialState: function() {
return {
count: 0
}
},

render: function() {
return (
<div className='increment-component'>
<h3 className='red-class'>Count: {this.state.count}.</h3>
<button onClick={this.incrementCount}>Boing!</button>
</div>
);
}
});


Here's how I render my components:

ReactDOM.render(
<StateComponent/>,
document.getElementById('state-point')
);
// similar calls for other components as needed


For what it's worth, I've tried the
document.getEle...
as the first JS fired, as the last JS fired, and as you see it now as part of a ReactJS component. The result is the same no matter where I place it in the code.

Answer

I believe the reason is due to how innerHTML works. It completely re-parse and replace the children DOM nodes (even if you're using += to just append the new ones), so it destroys all the event handlers previously attached to those DOM nodes, in your case the DOM subtree "managed" by React. For your case, you may need to consider using insertAdjacentHTML instead.

From the MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML):

"It does not reparse the element it is being used on and thus it does not corrupt the existing elements inside the element. "

Try with the following:

document.getElementById('body').insertAdjacentHTML('beforeend', xhr.responseText);