Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Universal React

Universal React

My explorations in universal React.

Jack Franklin

February 04, 2016
Tweet

More Decks by Jack Franklin

Other Decks in Programming

Transcript

  1. Universal React

    View full-size slide

  2. @Jack_Franklin,
    Pusher

    View full-size slide

  3. Source: GDS Blog, 2013

    View full-size slide

  4. "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."

    View full-size slide

  5. "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"

    View full-size slide

  6. 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

    View full-size slide

  7. Time and a place

    View full-size slide

  8. 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!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  11. React is a great
    fit

    View full-size slide

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

    View full-size slide

  13. // 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 });
    });

    View full-size slide

  14. 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

    View full-size slide

  15. 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

    View full-size slide

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

    View full-size slide

  17. Agnostic
    Components

    View full-size slide

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

    View full-size slide

  19. Rendering on the
    Client

    View full-size slide

  20. Updating our server template.



    <%- markup %>


    View full-size slide

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

    View full-size slide

  22. Same Components on
    client and server

    View full-size slide

  23. npm install --save-dev webpack babel-loader

    View full-size slide

  24. 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'
    }
    ]
    }
    }

    View full-size slide

  25. 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)

    View full-size slide

  26. 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 }

    );
    }
    }

    View full-size slide

  27. Demo Two!
    (Contrived example incoming!)

    View full-size slide

  28. 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?

    View full-size slide

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

    View full-size slide

  30. Don't be that
    person who breaks
    the web.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  33. 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.

    View full-size slide

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

    This is the index page

    );
    }
    }

    View full-size slide

  35. 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 = (



    );

    View full-size slide

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

    View full-size slide

  37. // 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';

    View full-size slide

  38. 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')
    }
    })
    });

    View full-size slide

  39. Let's break that
    down...

    View full-size slide

  40. // 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
    ...
    });

    View full-size slide

  41. 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) {
    ...
    }

    View full-size slide

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

    View full-size slide

  43. ...
    } 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 {
    ...
    }

    View full-size slide

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

    View full-size slide

  45. Let's add an about page!

    View full-size slide

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

    View full-size slide

  47. 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 = (




    );

    View full-size slide

  48. 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 }

    );
    }
    }

    View full-size slide

  49. With no client side bundle, this works perfectly:

    View full-size slide

  50. 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.

    View full-size slide

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

    View full-size slide

  52. (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!

    View full-size slide

  53. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  56. 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.

    View full-size slide

  57. 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 }

    );
    }
    }

    View full-size slide

  58. 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
    });
    });
    }

    View full-size slide

  59. • 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 %>

    View full-size slide

  60. And finally we can update our client side
    rendering too:
    import AsyncProps from 'async-props';
    ReactDOM.render(
    routes={routes}
    history={createBrowserHistory()}
    RoutingContext={AsyncProps}
    />,
    document.getElementById('app')
    )
    (Note: this looks much nicer in React Router v2)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  63. Server side by
    default?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide