Slide 1

Slide 1 text

MILAN 25-26 NOVEMBER 2016 { Universal JS Web Applications with React Luciano Mammino 1

Slide 2

Slide 2 text

WHO IS LUCIANO? nodejsdesignpatterns.com lmammino loige loige.co 2

Slide 3

Slide 3 text

AGENDA 1. The term "Universal" JS 2. Who & Why 3. Common problems and technologies 4. Building a frontend only Single Page App 5. Making it universal 3

Slide 4

Slide 4 text

ISOMORPHIC bit.ly/universaljs UNIVERSAL WHAT? 4

Slide 5

Slide 5 text

NOT ONLY FOR THE WEB... Desktop applications Mobile applications Hardware 5

Slide 6

Slide 6 text

ADVANTAGES OF UNIVERSAL JAVASCRIPT "JavaScript-only" development Maintainability Better SEO Faster "perceived" load time 6

Slide 7

Slide 7 text

IN THE WILD 7

Slide 8

Slide 8 text

IT LOOKS GREAT BUT... 8

Slide 9

Slide 9 text

MODULE SHARING Use Node.js modules in the browser. UMD 9

Slide 10

Slide 10 text

UNIVERSAL RENDERING Render the views of the application from the server (first request) and then in the browser (next requests) 10

Slide 11

Slide 11 text

UNIVERSAL ROUTING Recognise the view associated to the current route from both the server and the browser. 11

Slide 12

Slide 12 text

UNIVERSAL DATA RETRIEVAL Access data (and APIs) from both the server and the browser. AXIOS UNIVERSAL FETCH 12

Slide 13

Slide 13 text

UNIVERSAL STATE MANAGEMENT Manage changes on the state tree both on the server and the client... 13

Slide 14

Slide 14 text

FUTURISTIC/ALTERNATIVE JS?! 14

Slide 15

Slide 15 text

15

Slide 16

Slide 16 text

OK... LET'S STOP COMPLAINING AND BUILD SOMETHING! 16

Slide 17

Slide 17 text

WHAT TOOLS ARE WE GOING TO USE? 17

Slide 18

Slide 18 text

WHAT ARE WE GOING TO BUILD? judo-heroes.herokuapp.com bit.ly/judo-heroes-tutorial 18

Slide 19

Slide 19 text

19

Slide 20

Slide 20 text

20

Slide 21

Slide 21 text

21

Slide 22

Slide 22 text

curl -sS "https://judo-heroes.herokuapp.com/athlete/teddy-riner" 22

Slide 23

Slide 23 text

The data set // src/data/athletes.js const athletes = [ { 'id': 'driulis-gonzalez', 'name': 'Driulis González', 'country': { 'id': 'cu', 'name': 'Cuba', 'icon': 'flag-cu.png', }, 'birth': '1973', 'image': 'driulis-gonzalez.jpg', 'cover': 'driulis-gonzalez-cover.jpg', 'link': 'https://en.wikipedia.org/wiki/Driulis_González', 'medals': [ { 'year': '1992', 'type': 'B', 'city': 'Barcelona', 'event': 'Olympic Games', 'category': '-57kg { 'year': '1993', 'type': 'B', 'city': 'Hamilton', 'event': 'World Championships', 'category': ' { 'year': '1995', 'type': 'G', 'city': 'Chiba', 'event': 'World Championships', 'category': '-57 { 'year': '1995', 'type': 'G', 'city': 'Mar del Plata', 'event': 'Pan American Games', 'category { 'year': '1996', 'type': 'G', 'city': 'Atlanta', 'event': 'Olympic Games', 'category': '-57kg' // ... ], }, // ... ]; export default athletes; 23

Slide 24

Slide 24 text

REACT COMPONENTS 24

Slide 25

Slide 25 text

Layout component 25

Slide 26

Slide 26 text

// src/components/Layout.js import React from 'react'; import { Link } from 'react-router'; const Layout = (props) => (
{props.children}

This is a demo app to showcase universal Javascript with React and Express.

); export default Layout; 26

Slide 27

Slide 27 text

IndexPage component 27

Slide 28

Slide 28 text

// src/components/IndexPage.js import React from 'react'; import AthletePreview from './AthletePreview'; import athletes from '../data/athletes'; const IndexPage = (props) => (
{athletes.map( athleteData => )}
); export default IndexPage; 28

Slide 29

Slide 29 text

AthletePreview component 29

Slide 30

Slide 30 text

// src/components/AthletePreview.js import React from 'react'; import { Link } from 'react-router'; const AthletePreview = (props) => (

{props.name}

{props.medals.length}
); export default AthletePreview; 30

Slide 31

Slide 31 text

AthletePage component 31

Slide 32

Slide 32 text

// src/components/AthletePage.js import React from 'react'; import { Link } from 'react-router'; import NotFoundPage from './NotFoundPage'; import AthletesMenu from './AthletesMenu'; import Medal from './Medal'; import Flag from './Flag'; import athletes from '../data/athletes'; const AthletePage = (props) => { const id = props.params.id; const athlete = athletes.find((athlete) => athlete.id === id); if (!athlete) { return ; } const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return (

{athlete.name}

Olympic medalist from , born in {athlete.birth} (Find out more on Wikipedia).

Winner of {athlete.medals.length} medals:

    { athlete.medals.map((medal, i) => ) }
« Back to the index
); }; export default AthletePage; 32

Slide 33

Slide 33 text

// src/components/AthletePage.js // ... const AthletePage = (props) => { const id = props.params.id; const athlete = athletes.find((athlete) => athlete.id === id); if (!athlete) { return ; } const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return (

{athlete.name}

// ... 33

Slide 34

Slide 34 text

// src/components/AthletePage.js // ... Olympic medalist from , born in {athlete.birth} (Find out more on Wikipedia).

Winner of {athlete.medals.length} medals:

    { athlete.medals.map((medal, i) => ) }
« Back to the index
); }; export default AthletePage; 34

Slide 35

Slide 35 text

AthletesMenu component 35

Slide 36

Slide 36 text

// src/components/AthletesMenu.js import React from 'react'; import { Link } from 'react-router'; import athletes from '../data/athletes'; const AthletesMenu = (props) => ( {athletes.map(athlete => { return {athlete.name} ; })} ); export default AthletesMenu; 36

Slide 37

Slide 37 text

Flag component 37

Slide 38

Slide 38 text

// src/components/Flag.js import React from 'react'; const Flag = (props) => ( {props.showName && {props.name}} ); export default Flag; 38

Slide 39

Slide 39 text

Medal component 39

Slide 40

Slide 40 text

// src/components/Medal.js import React from 'react'; const medalTypes = { 'G': 'Gold', 'S': 'Silver', 'B': 'Bronze' }; const Medal = (props) => (
  • {props.type} {props.year} {props.city} ({props.event}) {props.category}
  • ); export default Medal; 40

    Slide 41

    Slide 41 text

    NotFoundPage component // src/components/NotFoundPage.js import React from 'react'; import { Link } from 'react-router'; const NotFoundPage = (props) => (

    404

    Page not found!

    Go back to the main page

    ); export default NotFoundPage; 41

    Slide 42

    Slide 42 text

    ROUTING 42

    Slide 43

    Slide 43 text

    2 ROUTES Index Page: / Athlete Page: /athlete/:id 43

    Slide 44

    Slide 44 text

    // src/Routes.js import React from 'react'; import { Route, IndexRoute } from 'react-router' import Layout from './components/Layout'; import IndexPage from './components/IndexPage'; import AthletePage from './components/AthletePage'; import NotFoundPage from './components/NotFoundPage'; const routes = ( ); export default routes; 44

    Slide 45

    Slide 45 text

    // src/components/AppRoutes.js import React from 'react'; import { Router, hashHistory } from 'react-router'; import routes from '../Routes'; const AppRoutes = (props) => ( window.scrollTo(0, 0)}/> ); export default AppRoutes; 45

    Slide 46

    Slide 46 text

    CLIENT APP 46

    Slide 47

    Slide 47 text

    // src/app-client.js import React from 'react'; import ReactDOM from 'react-dom'; import AppRoutes from './components/AppRoutes'; window.onload = () => { ReactDOM.render(, document.getElementById('main')); }; 47

    Slide 48

    Slide 48 text

    HTML WRAPPER 48

    Slide 49

    Slide 49 text

    // src/static/index.html Judo Heroes - A Universal JavaScript demo application with React
    49

    Slide 50

    Slide 50 text

    BUILD CONFIG (BABEL + WEBPACK) 50

    Slide 51

    Slide 51 text

    .babelrc import webpack from 'webpack'; import path from 'path'; const config = { entry: { js: './src/app-client.js' }, output: { path: path.join(__dirname, 'src', 'static', 'js'), filename: 'bundle.js' }, module: { loaders: [{ test: path.join(__dirname, 'src'), loaders: [{ loader: 'babel-loader' }] }] }, }; export default config; .webpack.config.babel.js { "presets": ["react", "es2015"] } 51

    Slide 52

    Slide 52 text

    LET'S BUILD IT! 52

    Slide 53

    Slide 53 text

    // src/server.js import path from 'path'; import { Server } from 'http'; import Express from 'express'; const app = new Express(); const server = new Server(app); // define the folder that will be used for static assets app.use(Express.static(path.join(__dirname, 'static'))); // start the server const port = process.env.PORT || 3000; const env = process.env.NODE_ENV || 'production'; server.listen(port, err => { if (err) { return console.error(err); } console.info(`Server running on http://localhost:${port} [${env}]`); }); Static Express server 53

    Slide 54

    Slide 54 text

    READY... LET'S TEST IT 54

    Slide 55

    Slide 55 text

    RECAP What we learned so far 1. Define views combining React components 2. Add Routing using React Router 3. Compiling our frontend bundle with Babel and Webpack 4. Run the app with a static Express server 55

    Slide 56

    Slide 56 text

    SERVER SIDE RENDERING AND ROUTING 56

    Slide 57

    Slide 57 text

    Using browser history 57

    Slide 58

    Slide 58 text

    // src/views/index.ejs Judo Heroes - A Universal JavaScript demo application with React
    <%- markup -%>
    Converting static index.html into a template 58

    Slide 59

    Slide 59 text

    Updating the server app 59

    Slide 60

    Slide 60 text

    // ... // universal routing and rendering app.get('*', (req, res) => { match( { routes, location: req.url }, (err, redirectLocation, renderProps) => { // in case of error display the error message if (err) { return res.status(500).send(err.message); } // in case of redirect propagate the redirect to the browser if (redirectLocation) { return res.redirect(302, redirectLocation.pathname + redirectLocation.search); } // generate the React markup for the current route let markup; if (renderProps) { // if the current route matched we have renderProps markup = renderToString(); } else { // otherwise we can render a 404 page markup = renderToString(); res.status(404); } // render the index template with the embedded React markup return res.render('index', { markup }); } ); }); 60

    Slide 61

    Slide 61 text

    THAT'S IT! LET'S TEST AGAIN 61

    Slide 62

    Slide 62 text

    RECAP What we learned so far 1. Create a Single Page Application with React and React Router 2. Add server side routing and rendering using React and React Router libraries in out Express app 62

    Slide 63

    Slide 63 text

    UNIVERSAL DATA RETRIEVAL api-proxy & async-props (COMPLETE CHAPTER in ) UNIVERSAL STATE MANAGEMENT Redux Node.js Design Patterns WHERE DO WE GO from here... Code: https://github.com/lmammino/judo-heroes-2 63

    Slide 64

    Slide 64 text

    THANKS! loige loige.co lmammino (Special thanks to , , Aleksandar Čambas & ) @cirpo @andreaman87 @quasi_modal 64