Front End Workshops Isomorphic Web Apps With ReactJs Marc Torrent mtorrent@visual-engin.com
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 obtain valid 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 HotModule ReplacementPlugin() CommonsChunkPlugin() DefinePlugin() ProvidePlugin() ExtractTextPlugin() Module
Avoiding FOUC - Webpack ExtractTextPlugin
Webpack code splitting // polyfill webpack require.ensure if (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 cycle 3. Render the vurrent Route with the current state 4. 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 component 3. Trigger fetch on the component 4. 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!
Workshop 27: Isomorphic web apps with ReactJS

Workshop 27: Isomorphic web apps with ReactJS

  • 1.
    Front End Workshops IsomorphicWeb Apps With ReactJs Marc Torrent mtorrent@visual-engin.com
  • 2.
    Isomorphic what?? First reviewReactJS + Redux recap workshops
  • 3.
    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 obtain valid 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.
  • 4.
    HTML Page Server SideRendering (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 !!!
  • 5.
    Server Side Renderingwith 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>
  • 6.
    Server Side Renderingwith 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); ...
  • 7.
    Server Side Rendering:server.js - Main Entry Point
  • 8.
    Server Side Rendering:server.js - HTML Template
  • 9.
    Client main entrypoint: client.js
  • 10.
    Webpack bundles Entry pointOutput 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 HotModule ReplacementPlugin() CommonsChunkPlugin() DefinePlugin() ProvidePlugin() ExtractTextPlugin() Module
  • 11.
    Avoiding FOUC -Webpack ExtractTextPlugin
  • 12.
    Webpack code splitting //polyfill webpack require.ensure if (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
  • 13.
    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
  • 14.
    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
  • 15.
    Dynamic Routing withnew 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???
  • 16.
    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
  • 17.
    Data fetching beforerendering 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
  • 18.
    REDIAL → Datafetching before rendering HOC exposing three actions to take BEFORE & AFTER rendering & only CLIENT SIDE.
  • 19.
    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 cycle 3. Render the vurrent Route with the current state 4. Get the state and make it available to the client
  • 20.
    React Router +Redux + Redial: provideHooks FAQ-Component.js FAQ-Actions.js
  • 21.
    React Router +Redux + Redial: Client Side 1. Listen to navigation events: browserHistory.listen() 2. Match routes to get the component 3. Trigger fetch on the component 4. Render the Component
  • 22.
  • 23.
    SEO friendly universalweb 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
  • 24.
    React-Helmet - ServerSide Rendering const head = Helmet.rewind(); head.title.toString()
  • 25.