Isomorphic what??
First review ReactJS + Redux recap workshops
Universal web apps - Isomorphic
Web Application Code (JS)
Server - Routing and Data gathering (Rest API)
ClientBrowser
HTTP Request,a new Page
With the data, inject it to our application code
render() and obtainvalid HTML
HTTP Response with HTML, Web Application Code and JSON Data
Web Application Code (JS)
Request Data, AJAX or Web sockets → JSON Routing
Isomorphic: the Client and the Server share the same code for routing, data gathering and rendering.
HTML Page
Server Side Rendering (SSR) with ReactJS
react-dom/server renderToString(ReactElement)
<MyReactComponent {...props} />
HTML + ReactJS Virtual DOM ID’sReactDOM.render(App(window.APP_PROPS),
document.getElementById('content')
No re-rendering as there’s no difference in Virtual DOM !!!
Server Side Rendering with Redux
react-dom/server const htmlEl = renderToString(ReactElement)
const store = createStore(reducers);
<Provider store={store}> <MyReactComponent {...props} /></ Provider>
No re-rendering as there’s no difference in Virtual DOM !!!
const initialState = store.getState();
const html = `<HTML> <body>
<script>window.initialState = JSON.Stringify(initialState);</script>
<div>${htmlEl}</div></body></HTML>`;
res.send(html);
Client.jsx
const initialState = window.initialState;const store = createStore(reducers, initialState);
<Provider store={store}> <MyReactComponent {...props} /></ Provider>
Server Side Rendering with React Router
import { createMemoryHistory, RouterContext, match } from 'react-router';
Finds the route from the current location and returns the component to be rendered.
Creates a Location object from the current url to be used by the match function.
Renders the component tree for a given router state.
import { createMemoryHistory, RouterContext, match } from 'react-router';
const history = createMemoryHistory(req.path);
match({ routes, history }, (err, redirectLocation, renderProps) => {…const InitialComponent = (<RouterContext {...renderProps} />);…const componentHTML = renderToString(InitialComponent);...
Server Side Rendering: server.js - Main Entry Point
Server Side Rendering: server.js - HTML Template
Client main entry point: client.js
Webpack bundles
Entry point Output
Loaders
main: [ ‘client.js’},vendor: { ‘react’, ‘react-dom’, ‘react-router’, ‘redux’}
.../main.js
.../vendor.js
.../[chunk_id].chunk.js
Babel_loader: ES6 → ES5
Style_loader | css_loader | sass_loader Plugins
HotModuleReplacementPlugin()CommonsChunkPlugin()DefinePlugin()ProvidePlugin()ExtractTextPlugin()
Module
Avoiding FOUC - Webpack ExtractTextPlugin
Webpack code splitting
// polyfill webpack require.ensureif (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require);
…
require.ensure([‘myModulesDependencies’], (require) => {const a = require('./ModuleA').default;const b = require('./ModuleB').default;
});
Output.../main.js
.../vendor.js
.../1.chunk.js
.../2.chunk.js
React Router - Configuration with Plain Routes
<Route path=”/” component={App}> <IndexRoute component={Dashboard} /> <Route path=”about” component={About} /> <Route path=”inbox” component={Inbox} > <Route path=”messages/:id” component={Message} /> </Route></Route>
{ path: ‘/’, component: App, indexRoute: { component: Dashboard }, childRoutes: [ { path: ‘about’, component: About }, { path: ‘inbox’, component: Inbox, childRoutes: [ { path: ‘messages/:id’, component: Message} ] } ]}
from ...
… to
React Router - Dynamic Routing & WebPack
{ path: ‘/’, component: App, indexRoute: { component: Dashboard },
getChildRoutes(location, cb) {require.ensure([], require => {
cb(null, [require(‘./About’).default,require(‘./Inbox’).default
]);});
}}
import About from ‘./components/About’;
{ path: ‘about’, component: About}
{ path: ‘inbox’, component: Inbox,
childRoutes: [{ path: ‘messages/:id’, component: Message}
]}
About.js Inbox.js
Dynamic Routing with new Reducers
At initialization we combine reducers to build the store...
const rootReducer = combineReducers({authReducer, otherReducer});
const store = createStore(rootReduer, initialState, applyMiddleware(...middleware);
…<Provider store={store}>
<Router routes={routes} /></Proider>…
But what happens if we load a route containing a new reducer that is needed for the components of that new
route???
Combining new Reducers - ReducerRegistry
{ path: ‘faq’,
getComponents:(location, cb) {require.ensure([‘./components/FAQ’, ‘./reducer’], require => {
const FAQ = require(‘./components/FAQ’).default;const faqReducer = require(‘./reducer’).default;store.replaceReducer(combineReducers({...existingRedu
cers, faqReducer}));
cb(null, FAQ);});
}}
Now we have the initial reducers combined with the new ones and applied to the store via store.replaceReducer
Data fetching before rendering
We need data to be accessible to Route Components before rendering those components.
In SSR it’s not possible to fetch data in componentWillMount or componentDidMount:1. componentDidMount is not available on server rendering.2. componentWillMount is called immediately before rendering and changes
in state won’t trigger a re-rendering → fetching data here doesn’t ensures that the render method will have all the data that is needed.
ReactRouter → onEnter = Function to be called before rendering a Route’s Component
REDIAL → Data fetching before rendering
HOC exposing three actions to take BEFORE & AFTER rendering & only CLIENT SIDE.
React Router + Redux + Redial: Server Side
1. Call trigger function which is an action creator that returns a Promise
2. State is update through usual Redux cycle3. Render the vurrent Route with the current state4. Get the state and make it available to the client
React Router + Redux + Redial: provideHooks
FAQ-Component.js
FAQ-Actions.js
React Router + Redux + Redial: Client Side
1. Listen to navigation events: browserHistory.listen()2. Match routes to get the component3. Trigger fetch on the component4. Render the Component
Bonus Track
SEO friendly universal web apps - React-Helmet
Isomorphic Web Apps is intended to improve SEO….... we need something to manage our meta tags !!!!
React Helmet is the solution for managing the meta tags of a Route Component
React-Helmet - Server Side Rendering
const head = Helmet.rewind(); head.title.toString()
Thank you!