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

Front End London: Universal React

Front End London: Universal React

Jack Franklin

May 26, 2016
Tweet

More Decks by Jack Franklin

Other Decks in Technology

Transcript

  1. Universal JavaScript

    View Slide

  2. @Jack_Franklin, Pusher

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. Why?

    View Slide

  8. Source: GDS Blog, 2013

    View Slide

  9. "Surprisingly, the propor2on 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 Slide

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

    View Slide

  11. 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 Slide

  12. View Slide

  13. Think about if this is right for you!

    View Slide

  14. Cu#ng Edge!
    We're really s*ll 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 Slide

  15. React paved the way

    View Slide

  16. View Slide

  17. View Slide

  18. Code and demos: h,ps:/
    /github.com/jackfranklin/universal-react-
    talk/tree/fel

    View Slide

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

    View Slide

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

    View Slide

  21. Tada!

    View Slide

  22. // 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 Slide

  23. renderToString
    When your HTML is going to be picked up by React on the client
    renderToSta*cMarkup
    When your HTML is never going to be edited by client-side React

    View Slide




  24. My App


    Hello from React


    View Slide

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

    View Slide

  26. Agnos&c Components

    View Slide

  27. Webpack

    View Slide

  28. Rendering on the Client

    View Slide

  29. Upda%ng our server template.






    View Slide

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

    View Slide

  31. Same Components on client and
    server

    View Slide

  32. npm install --save-dev webpack \
    babel-loader \
    babel-preset-es2015 \
    babel-preset-react \
    babel-yet-another-thing-lol-jk

    View Slide

  33. Create .babelrc
    {
    "presets": ["es2015", "react"]
    }

    View Slide

  34. 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$/,
    exclude: /node_modules/,
    loader: 'babel'
    }
    ]
    }
    }

    View Slide

  35. Run webpack:
    (PS: in produc.on we'd minify, etc!)
    > 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 con,nuous rebuilds)

    View Slide

  36. An interac*ve 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 Slide

  37. View Slide

  38. A read-only experience is way be3er
    than no experience.

    View Slide

  39. This isn't about giving every user
    100% func7onality every 7me.

    View Slide

  40. Rou$ng

    View Slide

  41. react-router
    The defacto, prac.cally standard rou.ng solu.on for React.
    h"ps:/
    /github.com/rackt/react-router

    View Slide

  42. First we need some more components, star1ng 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 Slide

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

    This is the index page

    );
    }
    }

    View Slide

  44. 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 Slide

  45. Match against the URL on the server.
    Gets a bit hairy, s-ck with me!
    React Router server side guide

    View Slide

  46. // 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 Slide

  47. 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 Slide

  48. Let's break that down...

    View Slide

  49. // take our app's routes, and the URL of the request
    match({ routes, location: req.url },
    (error, redirectLocation, renderProps) => {
    // 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 Slide

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

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

    View Slide

  52. ...
    } 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 app
    // and renderProps contains all the info
    // React Router needs to render our app
    markup: renderToString()
    });
    } else {
    ...
    }

    View Slide

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

    View Slide

  54. View Slide

  55. Let's add an about page!

    View Slide

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

    View Slide

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

  58. And some links...
    ...
    render() {
    return (

    My web 2.0 app
    Home
    About
    { this.props.children }

    );
    }
    ...

    View Slide

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

    View Slide

  60. Upda%ng 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 Slide

  61. View Slide

  62. Dealing with Data on the server and
    client

    View Slide

  63. (Caveat: this area is s-ll WIP)
    Lots of unknowns!

    View Slide

  64. 1. We want to be able to fetch data on the server and/or on the
    client.
    2. If the data is loaded on the server and rendered to the client, we
    ideally want to avoid making the request again.

    View Slide

  65. React Resolver

    View Slide

  66. code/with-react-resolver has an example.
    class IndexComponent extends React.Component {
    render() {
    return (

    This is the index page
    My github repo count: { this.props.github.public_repos }

    );
    }
    }
    export default resolve('github', (props) => {
    return fetch('https://api.github.com/users/jackfranklin').then((data) => {
    return data.json();
    });
    })(IndexComponent);

    View Slide

  67. Client:
    ...
    import { Resolver } from 'react-resolver';
    Resolver.render(() => {
    return
    }, document.getElementById('app'));

    View Slide

  68. Server:
    import { Resolver } from 'react-resolver';
    ...
    Resolver
    .resolve(() => )
    .then(({ Resolved, data }) => {
    res.render('index', {
    markup: renderToString(),
    data: JSON.stringify(data)
    });
    });

    View Slide

  69. Template:

    window.__REACT_RESOLVER_PAYLOAD__ = <%- data %>

    View Slide

  70. • Update the server side rendering to include
    window.__REACT_RESOLVER_PAYLOAD__.
    • Update the client side rendering to use React Resolver.
    • All data is resolved on the server, rendered and then used to
    populate data on the client.

    View Slide

  71. View Slide

  72. Code and Demos
    These slides and all the demos are on GitHub.
    jackfranklin/universal-react-talk
    Please send me ques,ons: @Jack_Franklin or [email protected]

    View Slide

  73. Universal JavaScript is here to stay.
    The techniques, libraries and approaches will change over 7me.
    There's plenty to figure out in this space!
    Long term I expect frameworks to do more, and it become even
    easier for developers to take advantage.

    View Slide

  74. Server side by default?

    View Slide

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

    View Slide

  76. If you dig React
    I can make you dig it more! Day long React workshop in London:
    • 10 June

    View Slide

  77. Thanks! @Jack_Franklin
    javascriptplayground.com
    h"p:/
    /speakerdeck.com/jackfranklin
    [email protected]

    View Slide