0

Everything is about isomorphic application. I'm using React with react-router module on server side for routing purposes and have following warning in browser console.

Warning: render(...): 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.

I have following routes schema defined on backend:

<Route path="/" component={App} >
    <IndexRoute component={Home} />
</Route>

App component:

module.exports = React.createClass({
  render : function() {
    return <html>
      <head></head>
      <body>        
        <div id="container">
          { this.props.children }
        </div>        
        <script src="/app/bundle.js"></script>
      </body>
    </html>
  }
});

Home component:

module.exports = React.createClass({
  render : function() {
    return <div>Any content here</div>
  }
});

After that I use on the frontend:

ReactDOM.render(<Home />, document.getElementById('container'));

Probable solution: If I understood correctly if I could render App component as static markup(renderToStaticMarkup) and Home component as a string (renderToString), then it would be ok.

Is it possible to implement something like that with react-router?

4
  • Have you googled "isomorphic react"? Commented Jan 13, 2016 at 18:38
  • 1
    @HenrikAndersson a lot Commented Jan 13, 2016 at 18:40
  • I want to render App component with React, not using pure html. Commented Jan 13, 2016 at 18:43
  • Why? If you have an evergreen browser sending just the script tag will automatically be wrapped in HTML, HEAD and BODY tags. And since React will manage all nodes for you your complexity increases since now you have to manage app state just to be able to render a component. Commented Jan 13, 2016 at 18:45

1 Answer 1

3

Assuming RR 1.03, your routing configuration looks fine.

Your app component should be like this:

module.exports = React.createClass({
  render : function() {
    return <html>
      <head></head>
      <body>        
        <div id="container">
          {React.cloneElement(this.props.children,
            {
              anyOtherPropsHere: 'blablah'
            }
          )}
        </div>        
        <script src="/app/bundle.js"></script>
      </body>
    </html>
  }
});

Your server response -> render should look something like this. (taken from the documentation)

import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from './routes'

serve((req, res) => {
  // Note that req.url here should be the full URL path from
  // the original request, including the query string.
  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (error) {
      res.status(500).send(error.message)
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
      // You can also check renderProps.components or renderProps.routes for
      // your "not found" component or route respectively, and send a 404 as
      // below, if you're using a catch-all route.
      res.status(200).send(renderToString(<RouterContext {...renderProps} />))
    } else {
      res.status(404).send('Not found')
    }
  })
})

And finally, somewhere on clientside load, do something like this. I've added the history library in this example to help.

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, match, RoutingContext } from 'react-router';
import history from 'utils/history';
import AppRoutes from '/app/AppRoutes';

// use this function to return a Route component with the right props
function createFluxComponent(Component, props) {
    props = Object.assign(props, {
        flux: window.flux.or.whatevs
    });

    return <Component {...props} />;
}

// add a global history listener perhaps?
history.listen(function(location) {
    console.log('Transition to--------------', location.pathname);
});

// add window.router polyfill for transitionTo
window.router = {
    transitionTo: function(t) {
        return history.pushState(null, t);
    }
};

// render your routing configuration, history and flux as  props
ReactDOM.render(<Router createElement={createFluxComponent} history={history} routes={AppRoutes}/>, document);

What is most important is that you render to string using the RouterContext and render props on the server side. You get the renderProps from the RR match function, which will run your routes against the config and produce the right component in renderProps. Then you'll simply render the client side with Router element and the router config to the document. Does this make sense? It should work without any invariants.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.