Search code examples
domreactjsprogressive-enhancementisomorphic-javascriptreactjs.net

Can ReactJS initialize its state based on a server-side rendered DOM?


I love how easy it is to do isomorphic JavaScript in React. The only thing that bothers me with how it works is that the data has to be downloaded twice by the browser. First in the DOM markup and again in JSON format to initialize state at runtime. All the data is already there in the DOM shouldn't React be able to rehydrate based on that alone?

screenshot of react component rendered server-side screenshot of the data duplicated in JSON format

I've done some experimentation doing progressive enhancement in KnockoutJS with a custom binding handler. I am hoping there is a way to so something similar with React.

//Custom binding to load the values into the view model from the DOM
ko.bindingHandlers.PE = {
    init: function(element, valueAccessor, allBindings) {
        var bindings = allBindings();
        if (typeof bindings.text === 'function') {
            bindings.text($(element).text());
        }
    }
};

Update

Putting logic into React that reads values from the DOM doesn't seem idiomatic. But nothing stops you from writing a little JavaScript to collect the values yourself! Given the example above you could do something like the following.

var comments = $('.media').map(function() {
  return {
    Author: $(this).find('.media-object').attr('src'),
    Text: $(this).find('.media-body').text()
  };
});

Then use that to initialize the react component client-side. I'm happy with that.


Solution

  • Rendering is a one-way transform in React. It does not store enough information in the DOM to reconstruct the data structures that created the DOM. The data-react-id attributes are used for React's diffing algorithm but not for storing information about user data.

    From your example (data-react-id's here are arbitrary, I wrote them by hand):

    var Comment = React.createClass({
      render: function() {
        return (
          <div className="comment">
            <h3 className="commentAuthor">{this.props.comment.Author}</h3>
            {this.props.comment.Text}
          </div>
        );
      }
    });
    
    React.render(
      <Comment comment={{"Author": "Shellie", "Text": "semper, dui lectus"}} />,
      ...
    );
    
    ...
    
    <!-- Output -->
    <div class="comment" data-react-id=".x.0">
      <h3 class="commentAuthor" data-react-id=".x.0.0">Shellie</h3>
      <span data-react-id=".x.0.1">semper, dui lectus</span>
    </div>
    

    There's no information in the DOM about where the string "Shellie" came from nor where "semper, dui lectus" came from. This example yields the same output:

    var Comment = React.createClass({
      render: function() {
        return (
          <div className="comment">
            <h3 className="commentAuthor">Shellie</h3>
            semper, dui lectus
          </div>
        );
      }
    });
    
    React.render(
      <Comment />,
      ...
    );
    
    ...
    
    <!-- Output -->
    <div class="comment" data-react-id=".x.0">
      <h3 class="commentAuthor" data-react-id=".x.0.0">Shellie</h3>
      <span data-react-id=".x.0.1">semper, dui lectus</span>
    </div>