My Problem
I am building an Isomorphic Application in React which first renders a component server-side, then takes advantage of React's intelligent re-rendering browser-side.
I have run into a situation where the DOM can become out of sync with the React component's state before React is able to first render browser-side. This can happen when the user is on a slow internet connection, and the react.js
file takes a while to download (which is also the reason I'm building an Isomorphic Application)
Example
Here is an example I put together to show this happening: http://jsfiddle.net/jesstelford/z4o44esb
- Run this example
- Togle the checkbox
- Click "Render React"
- The current React state is output in the console
- Note that it is still set to { done: false } which is incorrect
var TodoItem = React.createClass({
// ...
render: function() {
return (
<label>
<input type="checkbox" defaultChecked={this.state.done} onChange={this.onChange} />
{this.props.name}
</label>
);
}
});
// User toggles checkbox ON here, before React is rendered browser-side
// render using React browser-side
var renderedComponent = React.render(component, document.getElementById('content'));
// Incorrectly outputs { done: false }
console.log('React state:', renderedComponent.state);
Possible (Half) Solution
I have found one possible solution using React refs
: http://jsfiddle.net/jesstelford/z4o44esb/2
var TodoItem = React.createClass({
// ...
syncStateFromDOM: function() {
this.setDone(this.refs.done.getDOMNode().checked);
},
render: function() {
return (
<label>
<input ref="done" type="checkbox" defaultChecked={this.state.done} onChange={this.onChange} />
{this.props.name}
</label>
);
}
});
// User toggles checkbox ON here, before React is rendered browser-side
// render using React browser-side
var renderedComponent = React.render(component, document.getElementById('content'));
// Sync state from the DOM
renderedComponent.syncStateFromDOM()
// Correctly outputs { done: true }
console.log('React state:', renderedComponent.state);
The downsides to this approach are:
- the state is synced after the DOM is rendered
- Requires extra code external to the component itself to sync on first render
My Question
When pre-rendering a React Component server-side, is there any way to sync DOM state to that React Component before it has rendered browser-side, given the DOM has been manipulated by the user before React is loaded browser-side?
Thanks!
This is an intriguing question! At a high level, the problem is that React isn't catching the 'change' event that bubbled up to the top-level component when you clicked the checkbox, because it wasn't instantiated yet on the client side. Your half-solution handles this by manually simulating a call to
onChange
. I started thinking that you might need to queue onChange events... but then I realized that React already has everything you need.The "three-quarters" solution is to simply rename
syncStateFromDOM
tocomponentDidMount
, and don't even bother calling it manually. As per the docs, in the most recent versions of React,componentDidMount
is only called in the browser, and it's a lifecycle callback for after the component mounts (i.e. whenReact.render
is about to return). It's the perfect place for your use case. See: http://jsfiddle.net/qdt4z3w9/That solves your problem of code external to the component itself! But there's still state-setting going on after the original render takes place. Unfortunately, I think this is fundamentally the way React works - in order to be able to match up existing DOM nodes to refs, each component needs to be fully mounted first. But an extra Virtual DOM diff is a small price to pay, since it's designed to be lightning-quick.
Hope this helps!