Slide 1

Slide 1 text

Universal React

Slide 2

Slide 2 text

@Jack_Franklin, Pusher

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Why?

Slide 6

Slide 6 text

Source: GDS Blog, 2013

Slide 7

Slide 7 text

"Surprisingly, the proportion of people that have explicitly disabled JavaScript or use a browser that doesn't support JavaScript, only makes up a small slice of people that don't run JavaScript."

Slide 8

Slide 8 text

"Progressive enhancement has never been about users who've turned JavaScript off, or least it wasn't for me." Jake Archibald, "Progressive Enhancement Still Important"

Slide 9

Slide 9 text

Everyone has JS, right? • On a train / in a tunnel / etc • HTTP request hangs • Firewalls • ISP is interfering • A browser addon is messing with you • Your CDN is down Stuart Langridge's Flowchart

Slide 10

Slide 10 text

Time and a place

Slide 11

Slide 11 text

Cutting Edge! We're really still at the early stages of figuring out how this stuff works. Some of the code shown here isn't the easiest, or the APIs aren't that straight forward. This will change as we learn more. Don't expect this to be 100% smooth!

Slide 12

Slide 12 text

A standard React app: class MyApp extends React.Component { render() { ... } } ReactDOM.render( , document.getElementById('app') )

Slide 13

Slide 13 text

Server side: class MyApp extends React.Component { render() { ... } } ReactDOM.renderToString()

Slide 14

Slide 14 text

React is a great fit

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Demo One! Rendering HTML on the server from a React component

Slide 17

Slide 17 text

// some imports left out to save space import React from 'react'; import MyApp from './component'; import { renderToString } from 'react-dom/server'; const app = express(); app.get('*', (req, res) => { const markup = renderToString(); res.render('index', { markup }); });

Slide 18

Slide 18 text

renderToString When your HTML is going to be picked up by React on the client renderToStaticMarkup When your HTML is never going to be edited by client-side React

Slide 19

Slide 19 text

Going Client side • Shared set of components that are environment agnostic • A server rendering step (like we just saw) • A client rendering step • A bundler to generate our client side JavaScript

Slide 20

Slide 20 text

There's no requirement to actually go client side A server side generated React app with no client side JS is perfectly fine ;)

Slide 21

Slide 21 text

Agnostic Components

Slide 22

Slide 22 text

Webpack A JavaScript application bundler that will generate our client side build.

Slide 23

Slide 23 text

Rendering on the Client

Slide 24

Slide 24 text

Updating our server template.
<%- markup %>

Slide 25

Slide 25 text

Creating client.js: import React from 'react'; import ReactDOM from 'react-dom'; import MyApp from './component'; ReactDOM.render( , document.getElementById('app') );

Slide 26

Slide 26 text

Same Components on client and server

Slide 27

Slide 27 text

npm install --save-dev webpack babel-loader

Slide 28

Slide 28 text

Create webpack.config.js var path = require('path'); module.exports = { entry: path.join(process.cwd(), 'client.js'), output: { path: './public/', filename: 'build.js' }, module: { loaders: [ { test: /.js$/, loader: 'babel' } ] } }

Slide 29

Slide 29 text

Run webpack: > webpack Hash: 78c865d5593fe910f823 Version: webpack 1.12.12 Time: 4948ms Asset Size Chunks Chunk Names build.js 690 kB 0 [emitted] main + 160 hidden modules (Tip: webpack -w for continuous rebuilds)

Slide 30

Slide 30 text

An interactive component export default class MyApp extends React.Component { constructor() { super(); this.state = { count: 0 }; } onClick() { this.setState({ count: this.state.count + 1 }); } render() { return (
Click Me

Count: { this.state.count }

); } }

Slide 31

Slide 31 text

Demo Two! (Contrived example incoming!)

Slide 32

Slide 32 text

Most web apps still do a lot of rendering of data (from APIs, etc) and why would you prevent your non-JS users from that?

Slide 33

Slide 33 text

A read-only experience is way better than no experience.

Slide 34

Slide 34 text

Routing

Slide 35

Slide 35 text

Don't be that person who breaks the web.

Slide 36

Slide 36 text

react-router The defacto, practically standard routing solution for React. https://github.com/rackt/react-router

Slide 37

Slide 37 text

react-router 2.0.0-rc5 We're living on the bleeding edge here! React Router 2.0 upgrade guide

Slide 38

Slide 38 text

First we need some more components, starting with components/app.js: import React from 'react'; export default class AppComponent extends React.Component { render() { return (

My web 2.0 app

{ this.props.children }
); } } this.props.children are the nested routes.

Slide 39

Slide 39 text

And then components/index.js: import React from 'react'; export default class IndexComponent extends React.Component { render() { return (

This is the index page

); } }

Slide 40

Slide 40 text

Define our routes: import { Route } from 'react-router'; import React from 'react'; import AppComponent from './components/app'; import IndexComponent from './components/index'; export const routes = ( );

Slide 41

Slide 41 text

Match against the URL on the server. Gets a bit hairy, stick with me! React Router server side guide

Slide 42

Slide 42 text

// our newly defined routes import { routes } from './routes'; // match is responsible for matching routes against a URL // RouterContext renders the components in the matched routes import { match, RouterContext } from 'react-router';

Slide 43

Slide 43 text

app.get('*', (req, res) => { match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { if (error) { res.status(500).send(error.message) } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search) } else if (renderProps) { res.render('index', { markup: renderToString() }); } else { res.status(404).send('Not found') } }) });

Slide 44

Slide 44 text

Let's break that down...

Slide 45

Slide 45 text

// take our app's routes, and the URL of the request match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { // match figures out which routes match, and calls this callback with the arguments above // error : given if something went wrong matching a route // redirectLocation : returned if the URL matches a redirect // renderProps : given if a route was matched and we can render ... });

Slide 46

Slide 46 text

if (error) { // if there was an error, 500 with the error message // you might show a custom error HTML page here res.status(500).send(error.message) } else if (redirectLocation) { ... }

Slide 47

Slide 47 text

... } else if (redirectLocation) { // if we need to redirect, redirect to the new URL res.redirect(302, redirectLocation.pathname + redirectLocation.search) } else if (renderProps) { ... }

Slide 48

Slide 48 text

... } else if (renderProps) { // if we have renderProps that means we have a match and can render res.render('index', { // RouterContext is React Router's wrapper around our own components // and renderProps contains all the info React Router needs to render our app markup: renderToString() }); } else { ... }

Slide 49

Slide 49 text

} else { // if we get here, it's not an error, redirect or match // hence, 404! res.status(404).send('Not found') }

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

Let's add an about page!

Slide 52

Slide 52 text

components/about.js: import React from 'react'; export default class AboutComponent extends React.Component { render() { return

Rockstar developer

; } }

Slide 53

Slide 53 text

routes.js: import AppComponent from './components/app'; import IndexComponent from './components/index'; import AboutComponent from './components/about'; import React from 'react'; import { Route } from 'react-router'; export const routes = ( );

Slide 54

Slide 54 text

And some links... import React from 'react'; import { Link } from 'react-router'; export default class AppComponent extends React.Component { render() { return (

My web 2.0 app

Home About { this.props.children }
); } }

Slide 55

Slide 55 text

With no client side bundle, this works perfectly:

Slide 56

Slide 56 text

Updating the client side: import React from 'react'; import ReactDOM from 'react-dom'; import { Router, browserHistory } from 'react-router'; import { routes } from './routes'; ReactDOM.render( , document.getElementById('app') ) And then rerun webpack.

Slide 57

Slide 57 text

Demo Three!

Slide 58

Slide 58 text

Dealing with Data (If I have time...)

Slide 59

Slide 59 text

(Caveat: this area is still WIP) No one has quite figured out the best way to deal with loading data on the server and client with React components. This is very, very new terrirory and things are not settled yet. This will get a little messy!

Slide 60

Slide 60 text

I'm a fan of Async Props (also by the creator's of React Router). Warning: Async Props is not production ready yet, but I like the approach enough to consider it safe to demo. npm install --save async-props

Slide 61

Slide 61 text

Caveat 2: Async-Props and React Router 2 don't play 100% nicely So for this demo I've downgraded to React Router 1.

Slide 62

Slide 62 text

Going to use the fetch API, so need a polyfill: npm install --save isomorphic-fetch

Slide 63

Slide 63 text

AsyncProps expects a loadProps static method in your Read component that loads the data. All we need to do is define it, and AsyncProps will take care of the rest. We can then refer to that data on this.props without worrying about if it's loaded or not, because AsyncProps ensures that it is.

Slide 64

Slide 64 text

First, let's give components/index.js some data: export default class IndexComponent extends React.Component { // a stage 1 proposal for ES.next static loadProps(params, cb) { fetch('https://api.github.com/users/jackfranklin').then((data) => { return data.json(); }).then((github) => { cb(null, { github }); }).catch((e) => { cb(e); }); } render() { return (

My github repo count: { this.props.github.public_repos }

); } }

Slide 65

Slide 65 text

Then we update our server rendering: // within the `match` callback: } else if (renderProps) { // loadPropsonServer is provided by async-props loadPropsOnServer(renderProps, (err, asyncProps, scriptTag) => { res.render('index', { // we now render AsyncProps markup: renderToString(), scriptTag }); }); }

Slide 66

Slide 66 text

• loadPropsOnServer gives us two items in the callback: asyncProps and scriptTag. • The scriptTag is needed to transfer the data from server to client. We change our index.ejs template:
<%- markup %>
<%- scriptTag %>

Slide 67

Slide 67 text

And finally we can update our client side rendering too: import AsyncProps from 'async-props'; ReactDOM.render( , document.getElementById('app') ) (Note: this looks much nicer in React Router v2)

Slide 68

Slide 68 text

Demo 4!

Slide 69

Slide 69 text

Deep Breath These slides and all the demos are on GitHub. Please send me questions: @Jack_Franklin or [email protected].

Slide 70

Slide 70 text

Universal JavaScript is here to stay. The techniques, libraries and approaches will change over time. There's plenty to figure out in this space!

Slide 71

Slide 71 text

Server side by default?

Slide 72

Slide 72 text

Further Reading • Universal React on 24ways • How we built the new gocardless.com • GoCardless.com repo • Universal React Example app

Slide 73

Slide 73 text

If you dig React I can make you dig it more! Day long React workshops in London: • 16 March • 10 June

Slide 74

Slide 74 text

Thanks! @Jack_Franklin http://speakerdeck.com/jackfranklin