Search code examples
javascriptreactjsruby-on-railsredux

Rails 5 Redux React Server Side Rendering gives client side JavaScript warning 'Replacing React-rendered children with a new...'


UPDATE: Problem isolated - react-rails and npm versions of React were different - fix in own answer below

Using rails-react on rails 5 and getting this warning:

Replacing React-rendered children with a new root component. If you
intended to update the children of this node, you should instead have
the existing children update their state and render the new
components instead of calling ReactDOM.render.

The site seems to work fine, but obviously something is up :)

index.html.erb:

<div class="item">
  <%= react_component('WeatherRoot', {}, { :prerender => true } ) %>
</div>

components.js:

window.React = require('react');
window.ReactDOM = require('react-dom');
window.WeatherRoot  = require('./components/containers/WeatherRoot.es6').default;

WeatherRoot.es6.js

import React, { Component } from 'react';
import { render } from 'react-dom'
import { Provider } from 'react-redux';
import WeatherContainer from './WeatherContainer.es6';
import configureStore from '../store/configureStore.es6';

import {fetchWeather} from '../actions/weather.es6';

const store = configureStore();

// Request Data as Early as Possible
store.dispatch(fetchWeather());

export default class WeatherRoot extends Component {
  render() {
    return (
      <Provider store={store}>
        <WeatherContainer />
      </Provider>
    );
  }
}

WeatherContainer.es6

import { connect } from 'react-redux'
import Weather from '../components/Weather.es6';


export function WeatherContainerImpl(props){
    return (<div>HELLO</div>);
}

const mapStateToProps = (state, ownProps) => {
  return {
    weather: state.weather.data
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    dispatch
  }
}

const WeatherContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(WeatherContainerImpl);

export default WeatherContainer;

Page Renders:

<div data-react-class="WeatherRoot" data-react-props="{}">
  <div data-reactroot="" data-reactid="1" data-react-checksum="-951512882">HELLO</div>
</div>

Replaced By Client Side Rendering:

<div data-react-class="WeatherRoot" data-react-props="{}">
  <div data-reactid=".0">HELLO</div>
</div>

Debugging ReactMount.js shows this method returning true being the source of the warning:

/**
 * True if the supplied DOM node has a direct React-rendered child that is
 * not a React root element. Useful for warning in `render`,
 * `unmountComponentAtNode`, etc.
 *
 * @param {?DOMElement} node The candidate DOM node.
 * @return {boolean} True if the DOM element contains a direct child that was
 * rendered by React but is not a root element.
 * @internal
 */
function hasNonRootReactChild(node) {
  var reactRootID = getReactRootID(node);
  return reactRootID ? reactRootID !== ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false;
}

Basically the node has an id of "1" from the server side rendering and

ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID)

Returns null with this implementation:

(id) {
  if (id && id.charAt(0) === SEPARATOR && id.length > 1) {
    var index = id.indexOf(SEPARATOR, 1);
    return index > -1 ? id.substr(0, index) : id;
  }
  return null;
}

Looks like the id would need to begin with a . to pass?

Why don't the server side rendered ids begin with a .?

Maybe I have different versions of react? but both look like 14.8

Adding some debugging:

export function WeatherContainer(props){
  console.log("WeatherContainer", React.version)
  return (<div>HELLO</div>);
}

Shows these logs:

WeatherContainer 15.2.1
WeatherContainer 0.14.8

!!!!

Now how to figure out how I'm getting two versions?


Solution

  • OK I found the problem

    By adding a console log I was able to see that the server version of React was different from the client side version

    export function WeatherContainer(props){
      console.log("WeatherContainer", React.version)
      return (<div>HELLO</div>);  
    }
    

    Gave:

    WeatherContainer 15.2.1
    WeatherContainer 0.14.8
    

    NOTE: The console log is visible client side because of the replay_console setting in application.rb

    config.react.server_renderer_options = {
        files: ["react-server.js", "components.js"], # files to load for prerendering
        replay_console: true,                 # if true, console.* will be replayed client-side
        }
    

    react-rails-1.8.1 bundles react 15.2.1

    So in package.json I forced react and react-dom to version 0.14.3 with

    npm install -S [email protected]
    npm install -S [email protected]
    

    and in Gemfile I forced react-rails to 1.5.0 with

    gem 'react-rails', '1.5'
    

    Now both are react 0.14.3

    and the warning is gone