▪ Rich client interactions ▪ Faster page navigation ▪ No re-downloading assets every page ▪ No re-fetching data every page ▪ Just load the new data and new assets if needed Single Page Apps
First Principle of Rich Web Apps “server-rendered apps vs single-page apps: If we want to optimize for the best possible user experience and performance, giving up one or the other is never a good idea.” - Guillermo Rauch http://rauchg.com/2014/7-principles-of-rich-web-applications/
Mojito was Yahoo’s first take on isomorphic MVC ● Used YUI on the server and the client Lessons learned: ● Browser abstractions are too slow for the server ● Synchronizing server, client, and DOM was hard ● Using YUI’s module format was problematic in node.js
Low Hanging Fruit ▪ Use CommonJS or ES6 instead of mixing formats ▪ Use polyfills instead of abstractions where possible ▪ es5-shim, intl.js, etc. ▪ Use libraries that are shareable ▪ lodash, superagent, moment, etc. ▪ Use bundling for building your javascript ▪ e.g. webpack or browserify
React ▪ Works on the server and the client with one simple API change: ▪ Client: React.render(Component(), domElement, callback); ▪ Server: var html = React.renderToString(Component()); ▪ Clean programmatic page composition using components ▪ DOM synchronization is easy, now only worry about state management
Flux ▪ Provides clean unidirectional application flow ▪ Describes where state management happens (stores) ▪ Decouples model dependencies by using event handlers
Working Backwards Dispatcher Stores React Component React Components Stores are populated by actions from the dispatcher Component needs to render based on a certain state from a store
Working Backwards Action Creator Actions are dispatched through the dispatcher from action creators Dispatcher Stores React Component React Components Stores are populated by actions from the dispatcher Component needs to render based on a certain state from a store
var React = require('react'); var ChatApp = require('./components/ChatApp.jsx'); var showMessages= require('./actions/showMessages'); server.get('/', function (req, res, next) { showMessages(); var html = React.renderToString(ChatApp()); res.write('Chat'); res.write(''); res.write('' + html + ''); res.write(''); res.write('') res.write(''); res.end(); }); server.js
Scoping the Dispatcher ▪ Now that the store is a class, registration of the handler needs to be done for the instance ▪ Dispatches should be scoped to the request too, so that data isn’t sent to every request’s store instance
Dispatchr ▪ Register classes to the dispatcher instead of static functions ▪ Instantiate a new Dispatchr per request ▪ Dispatchr creates instances of stores as they are needed and dispatches the actions https://github.com/yahoo/dispatchr
var Dispatcher = require('./lib/dispatcher'); server.get('/', function (req, res, next) { var dispatcher = new Dispatcher(); showMessages({}, function (err) { if (err) { next(err); return; } var html = React.renderToString(ChatApp()); res.write('Chat'); res.write(''); res.write('' + html + ''); res.write(''); res.write('') res.write(''); res.end(); }); }); server.js
var Dispatcher = require('./lib/dispatcher'); server.get('/', function (req, res, next) { var dispatcher = new Dispatcher(); showMessages(dispatcher, {}, function (err) { if (err) { next(err); return; } var html = React.renderToString(ChatApp()); res.write('Chat'); res.write(''); res.write('' + html + ''); res.write(''); res.write('') res.write(''); res.end(); }); }); server.js
Overview of Changes ▪ Store instances instead of singletons ▪ Dispatcher instance instead of singleton ▪ Passing dispatcher instance to action creators and components that need it
var React = require('react'); var ChatApp = require('./components/ChatApp.jsx'); var Dispatcher = require('./lib/dispatcher'); var dispatcher = new Dispatcher(); var showMessages = require('./actions/showMessages'); showMessages(dispatcher, {}, function (err) { if (err) { throw err; } React.render(ChatApp({ dispatcher: dispatcher }), document.getElementById('app'), function (err) { if (err) { throw err; } }); }); client.js
var React = require('react'); var ChatApp = React.createFactory(require('./components/ChatApp.jsx')); var Dispatcher = require('./lib/dispatcher'); var dispatcher = new Dispatcher(); dispatcher.rehydrate(window.App); React.render(ChatApp({ dispatcher: dispatcher }), document.getElementById('app'), function (err) { if (err) { throw err; } }); client.js
Naming between actions, action creators, and component interactions can be confusing I’ve probably slipped up countless times throughout this presentation
Dispatcher (or some scoped wrapper) needs to be passed around to all components that need it React’s undocumented context could solve this, but unsure of stability of the API
Knowing which actions we should execute is based on which components are going to be rendered No way to know which components are being rendered. Need some step between a full component hierarchy and being rendered to a string.
fluxible-plugin-routr Adds isomorphic routing interfaces to your fluxible-app ● Shared routes between server and client ● Shared route matching logic ● Create paths from components using routes names and parameters ● Example: context.makePath(‘user’, { id: 1 }) // returns /user/1
fluxible-plugin-fetchr Adds isomorphic CRUD interfaces to your fluxible-app ● Create server side web services ● Expose them via an XHR endpoint ● Access your services from an action with the same code both server and client ● Example: context.service.read(‘messages’, {/*params*/}, callback) ● Client uses XHR, server calls service directly