-
-
Notifications
You must be signed in to change notification settings - Fork 631
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Serverside rendering and Async Routes #477
Comments
@kevinpanxc We can hack on clientStartup.js and put together a PR. What do you want it to do? |
@justin808 I'm not sure what would be the best way to do it, but perhaps an API to allow me to specify a wrapper function around Main point is to have greater support for the dynamic routing feature of React-router. |
One thing I can see that would be iffy is how we pass the history and routes objects to React-router's Any ideas on how we can cleanly do it? |
Wait, maybe a good way would be to adapt ComponentRegistry.js to take in promises, then we wouldn't have to deal with passing esoteric values to |
@kevinpanxc Please create a demo app and a PR that demonstrates what you'd like to do. |
@kevinpanxc are you sure you can't do this with how ReactOnRails is currently written? I'm doing completely different things when server rendering versus client rendering (including using match) with ReactOnRails. The key is using a import ReactOnRails from 'react-on-rails';
import React from 'react';
import { browserHistory, RouterContext, Router, Route, match } from 'react-router';
import routes from './routes'; // custom to your app
const getClientMyComponent = (props, railsContext) => {
// feel free to use props/railsContext here
return <Router history={history}>{routes}</Router>;
};
const getServerMyComponent = (props, railsContext) => {
// feel free to use props/railsContext here
const { location } = railsContext;
let retVal;
const matchCallback = (error, redirectLocation, renderProps) => {
retVal = (error || redirectLocation)
? { error, redirectLocation }
: retVal = <RouterContext {...renderProps} />;
};
match({ routes, location }, matchCallback);
return retVal;
};
const MyComponent = (props, railsContext) => {
const isServerSide = typeof window === 'undefined';
return isServerSide
? getServerMyComponent(props, railsContext)
: getClientMyComponent(props, railsContext);
};
ReactOnRails.register({ MyComponent }); If you use my code sample, but convert the const getClientMyComponent = (props, railsContext) => {
// feel free to use props/railsContext here
const { location } = railsContext;
let retVal;
const matchCallback = (error, redirectLocation, renderProps) => {
retVal = (error || redirectLocation)
? { error, redirectLocation }
: retVal = <Router {...renderProps} />;
};
match({ routes, location }, matchCallback);
return retVal;
}; |
@robwise I will try that and report the results in a couple days... caught up with school work right now. Thanks for the heads up! 👍 |
I'm pretty sure it's not possible b/c of ExecJS.
Regards, Alex Fedoseev |
@alexfedoseev Should we close this one as basically impossible b/c we're not using a separate Node server for rendering? Or maybe this is something a Node server could handle? |
@justin808 Yeah, it's impossible w/o separate Node server which accepts http requests and send http responses. In the node renderer implemented by @alleycat-at-git we simply |
@kevinpanxc Any compelling use to case to suggest that we need to support "Serverside rendering and Async Routes"? |
@justin808 I'm in the exact same situation as OP. I'm using server rendering and webpack code splitting, and when I request a page that has a chunk that needs to be requested asynchronously, React gives me an error saying that the client generated code doesn't match the server generated code. This happens because ReactRouter renders a comment while it waits for the webpackJsonP request to be completed. The solution is to wait until the chunk is fetched before doing the initial render. Right now, I've solved this problem by not registering the component that contains the router, and rendering it myself. It works fine, but it's kind of an ugly solution, because now ReactOnRails throws an error complaining that I haven't registered the component. What would be better is if ReactOnRails provided one more registering API, something like page.html.erb <%= redux_store_hydration_data %>
<%= react_component("NavigationApp", prerender: true) %>
<%= react_component("RouterApp", prerender: true) %> clientRegistration.js import ReactOnRails from 'react-on-rails';
import NavigationApp from './NavigationApp';
import RouterApp from './renderRouterApp';
import applicationStore from '../store/applicationStore';
ReactOnRails.registerStore({applicationStore});
ReactOnRails.register({NavigationApp});
ReactOnRails.registerRenderer({RouterApp}); renderRouterApp.jsx import ReactOnRails from 'react-on-rails';
import React from 'react';
import ReactDOM from 'react-dom';
import Router from 'react-router/lib/Router';
import match from 'react-router/lib/match';
import browserHistory from 'react-router/lib/browserHistory';
import { Provider } from 'react-redux';
import routes from '../routes/routes';
const renderRouterApp = (props, railsContext, domNodeId) => {
const store = ReactOnRails.getStore('applicationStore');
match({ history: browserHistory, routes }, (error, redirectionLocation, renderProps) => {
if (error) {
throw error;
}
const component = (
<Provider store={store}>
<Router {...renderProps} />
</Provider>
);
ReactDOM.render(component, document.getElementById(domNodeId));
});
};
export default renderRouterApp; I plan on implementing this myself and submitting a pull request in the next week or so. Any feedback? |
@kevinpanxc How do you plan to "wait" on server in ExecJS env? |
There's no need to wait on the server side. You would just call register like normal. |
@jtibbertsma I look forward to seeing your PR. |
@jtibbertsma From this part I assume you're just doing router's work manually on the server, removing the router related parts from the bundle. |
No, I'm using the router on the server as well. On the client side, I don't register the component and render it myself. On the server side, I register it like normal and let react on rails do the rendering. |
@jtibbertsma I don't get then. If you're using code splitting on server you are calling |
Oh, I see what your asking. I've actually got separate route files for server and client. In the client routes I use |
I see, ok. |
The line doc/additional-reading/code-splitting.md should be changed to a url when/if this proposal gets accepted. This proposal was originally desribed here: shakacode#477
Made the following changes to the node package: * ComponentRegistry.js: Modified register to detect generator functions with three arguments. Set the isRenderer key to true for these functions. * clientStartup.js: Added logic to delegate to renderer functions. * createReactElement.js: Now accepts an object instead of a component name. * serverRenderReactComponent.js: Throws an error if attempting to render a renderer function. * ReactOnRails.js: Change render function to call createReactElement with the component object. Doc changes: * README.md: Added section about renderer function under the section on generator functions. Moved the section on generator functions from the 'ReactOnRails View Helpers API' section to the 'Globally Exposing Your React Components' section. * Added a file code-splitting.md that describes how to use renderer functions to do code splitting with server rendering. Tests: * ComponentRegistry.test.js: Modified existing test cases to expect the isRenderer key to be false. Added a few test cases related to renderer functions. * serverRenderReactComponent.test.js: Show that an error gets thrown if trying to server render with a renderer function. * spec/dummy: Added two examples using rendering functions, one of which implements code splitting. Added three test to integration_spec.rb. Resolves: shakacode#477
Made the following changes to the node package: * ComponentRegistry.js: Modified register to detect generator functions with three arguments. Set the isRenderer key to true for these functions. * clientStartup.js: Added logic to delegate to renderer functions. * createReactElement.js: Now accepts an object instead of a component name. * serverRenderReactComponent.js: Throws an error if attempting to render a renderer function. * ReactOnRails.js: Change render function to call createReactElement with the component object. Doc changes: * README.md: Added section about renderer function under the section on generator functions. Moved the section on generator functions from the 'ReactOnRails View Helpers API' section to the 'Globally Exposing Your React Components' section. * Added a file code-splitting.md that describes how to use renderer functions to do code splitting with server rendering. Tests: * ComponentRegistry.test.js: Modified existing test cases to expect the isRenderer key to be false. Added a few test cases related to renderer functions. * serverRenderReactComponent.test.js: Show that an error gets thrown if trying to server render with a renderer function. * spec/dummy: Added two examples using rendering functions, one of which implements code splitting. Added three test to integration_spec.rb. Resolves: shakacode#477
Made the following changes to the node package: * ComponentRegistry.js: Modified register to detect generator functions with three arguments. Set the isRenderer key to true for these functions. * clientStartup.js: Added logic to delegate to renderer functions. * createReactElement.js: Now accepts an object instead of a component name. * serverRenderReactComponent.js: Throws an error if attempting to render a renderer function. * ReactOnRails.js: Change render function to call createReactElement with the component object. Doc changes: * README.md: Added section about renderer function under the section on generator functions. Moved the section on generator functions from the 'ReactOnRails View Helpers API' section to the 'Globally Exposing Your React Components' section. * Added a file code-splitting.md that describes how to use renderer functions to do code splitting with server rendering. Tests: * ComponentRegistry.test.js: Modified existing test cases to expect the isRenderer key to be false. Added a few test cases related to renderer functions. * serverRenderReactComponent.test.js: Show that an error gets thrown if trying to server render with a renderer function. * spec/dummy: Added two examples using rendering functions, one of which implements code splitting. Added three test to integration_spec.rb. Resolves: shakacode#477
I've been trying to get React-router's dynamic routing to work with ReactOnRails and found that it's impossible right now with server-side rendering turned on. The problem is that the client-generated markup and the server-generated markup are different. This is expected as code splitting implies the client needs to make ajax requests to fetch new code to render React components. Client markup will certainly be different from server markup until the requests are complete and the new components are rendered.
React-router has a solution for this and they outline it here: React-router async routes.
Specifically, I would like to do this:
As mentioned in the previous link, we will now be waiting for all async behaviour to be resolved before comparing client/server markups. This allows for the client to load the split code and render any missing React components before the markup comparison and React will not complain of a mismatch.
However, as I see it from clientStartup.js, the render method is highly inflexible and it is impossible to place a wrapper around it.
Will there be greater support for code splitting (specifically client/server markup parity) in ReactOnRails in the future?
Thanks!
Other than this, it's been a pleasure to work with ReactOnRails so far.
The text was updated successfully, but these errors were encountered: