4

I have create this sample repo that use rails (v4.2.6) with react-rails (v1.6.2) and react-router (v2.0.0-rc5): https://github.com/pioz/rails_with_react_and_react_router_example

In the file app/views/application/react_entry_point.html.erb I render the component MountUp with

<%= react_component('MountUp', {}, {prerender: false}) %>

The component MountUp render my router:

class MountUp extends React.Component {
  render() {
    return(
      <Router history={History}>
        <Route path="/" component={App}>
          <IndexRoute component={Index} />
          <Route path="/contact" component={Contact}/>
          <Route path="/about" component={About}/>
        </Route>
      </Router>
    )
  }
}

All works fine, but if I change the option prerender: true I get a strange error React::ServerRendering::PrerenderError in Application#react_entry_point:

Encountered error "Error: Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings." when prerendering MountUp with {}
Object.invariant [as default] ((execjs):21983:16)
createHashHistory ((execjs):24108:130)
(execjs):22633:20
wrapDeprecatedHistory ((execjs):25285:61)
createRouterObjects ((execjs):25259:23)
componentWillMount ((execjs):25228:38)
ReactCompositeComponentMixin.mountComponent ((execjs):8138:13)
wrapper [as mountComponent] ((execjs):3131:22)
Object.ReactReconciler.mountComponent ((execjs):6583:36)
ReactCompositeComponentMixin.mountComponent ((execjs):8153:35)
/Users/pioz/.rvm/gems/ruby-2.3.0/gems/execjs-2.6.0/lib/execjs/external_runtime.rb:39:in `exec'
...

How can I render this app server side? Is this the right way to do this?

Pioz
  • 6,051
  • 4
  • 48
  • 67

2 Answers2

2

Found a solution: we need two version of the component MountUp: a client version that use browser history and a server version that use a fake memory history. Here the two version of the component:

// client version
class MountUp extends React.Component {
  render() {
    return(
      <Router history={History}>
        <Route path="/" component={App}>
          <IndexRoute component={Index} />
          <Route path="/contact" component={Contact}/>
          <Route path="/about" component={About}/>
        </Route>
      </Router>
    )
  }
}


// server version
class MountUp extends React.Component {
  render() {
    return(
      <Router history={createMemoryHistory(this.props.path)}>
        <Route path="/" component={App}>
          <IndexRoute component={Index} />
          <Route path="/contact" component={Contact}/>
          <Route path="/about" component={About}/>
        </Route>
      </Router>
    )
  }
}

We need also to create the memory history with the url path equal to the request: to do this we can pass to the component a new prop path with the path of the request:

<%= react_component('MountUp', {path: request.path}, {prerender: true}) %>
Pioz
  • 6,051
  • 4
  • 48
  • 67
  • In the server side case, when the page is rendered on the client it still uses memoryHistory and because of that if we click on a link the url does not change but the content changes. Is there a way to get around it? – Abhay Kumar Oct 25 '16 at 03:53
  • 1
    You need to use 2 different Component MountUp, one for the client side and one on the server side... application.js that include mountup_client.js e server.js that include mountup_server.js – Pioz Oct 25 '16 at 11:40
1

I think telling it not to prerender won't help

prerender: false

Also, do as it suggests and grab the dev version so it tells you a bit more

use the non-minified dev environment for the full error message

You are telling it to build the routes based on the History object, which is supposed to say where the user has requested to be (the url). On the server side, you will need to somehow construct an object that will mimic the browser history with one entry: the requested url.

I have done this in node using Redux using this:

import createHistory from 'history/lib/createMemoryHistory';


var reducer = combineReducers(reducers);

const store = compose(
  applyMiddleware(promiseMiddleware, thunk),
  reduxReactRouter({routes, createHistory})
)(createStore)(reducer, {});

But you will need to take a different approach in order to get the Rails request details into the history store.

A better error message from the dev library is the next thing you need.

Matt Gibson
  • 14,616
  • 7
  • 47
  • 79