Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I sync state from the DOM to my React Component in an Isomorphic Application?

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!

like image 998
Jess Telford Avatar asked Dec 15 '14 22:12

Jess Telford


People also ask

How does react interact with the DOM?

React will update the entire Virtual DOM. It'll then compare the Virtual DOM before updating, with the one after updating, to identify what objects have been changed. It uses the Diffing Algorithm. Only the changed objects will get updated on the Real DOM.

What is isomorphic in react?

In Isomorphic process,when Developer authorizes client facing module ,there is a dependency of the module which requires a file containing secret data,Application is bundled with that file and sent to client with all secret and private data which an attacker can easily access by searching the bundle file .


1 Answers

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 to componentDidMount, 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. when React.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!

like image 166
btown Avatar answered Oct 02 '22 20:10

btown