Universal JavaScript Web Applications with React - Luciano Mammino - Codemotion Milan 2016

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

Transcript of Universal JavaScript Web Applications with React - Luciano Mammino - Codemotion Milan 2016

MILAN 25-26 NOVEMBER 2016

{ Universal JS Web Applications with ReactLuciano Mammino

1

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

ISOMORPHIC

bit.ly/universaljs

UNIVERSAL WHAT?

4

NOT ONLYFOR THE WEB...

Desktop applications

Mobile applications

Hardware

5

ADVANTAGESOF UNIVERSAL JAVASCRIPT

"JavaScript-only" development

Maintainability

Better SEO

Faster "perceived" load time

6

IN THE WILD

7

IT LOOKS GREAT BUT...

8

UNIVERSAL RENDERING

Render the views of the application from

the server (first request) and then in the

browser (next requests)

10

UNIVERSAL ROUTING

Recognise the view associated to the

current route from both the server and the

browser.

11

UNIVERSAL DATA RETRIEVAL

Access data (and APIs) from both the server

and the browser.

AXIOS UNIVERSALFETCH

12

UNIVERSAL STATEMANAGEMENT

Manage changes on the state tree both on

the server and the client...

13

FUTURISTIC/ALTERNATIVE JS?!

14

15

OK...LET'S STOP COMPLAININGAND BUILD SOMETHING!

16

WHAT ARE WEGOING TO BUILD?

judo-heroes.herokuapp.com

bit.ly/judo-heroes-tutorial

18

19

20

21

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

22

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': '-57kg' { 'year': '1995', 'type': 'G', 'city': 'Chiba', 'event': 'World Championships', 'category': '-57kg' { '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

REACTCOMPONENTS

24

Layout component

25

// src/components/Layout.js

import React from 'react';import { Link } from 'react-router';

const Layout = (props) => ( <div className="app-container"> <header> <Link to="/"> <img className="logo" src="/img/logo-judo-heroes.png"/> </Link> </header> <div className="app-content">{props.children}</div> <footer> <p> This is a demo app to showcase <strong>universal Javascript</strong> with <strong>React</strong> and <strong>Express</strong>. </p> </footer> </div>);

export default Layout;

26

IndexPage component

27

// src/components/IndexPage.js

import React from 'react';import AthletePreview from './AthletePreview';import athletes from '../data/athletes';

const IndexPage = (props) => ( <div className="home"> <div className="athletes-selector"> {athletes.map( athleteData => <AthletePreview key={athleteData.id} {...athleteData} /> )} </div> </div>);

export default IndexPage;

28

AthletePreview component

29

// src/components/AthletePreview.js

import React from 'react';import { Link } from 'react-router';

const AthletePreview = (props) => ( <Link to={`/athlete/${props.id}`}> <div className="athlete-preview"> <img src={`img/${props.image}`}/> <h2 className="name">{props.name}</h2> <span className="medals-count"> <img src="/img/medal.png"/> {props.medals.length} </span> </div> </Link>);

export default AthletePreview;

30

AthletePage component

31

// 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 <NotFoundPage/>; } const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( <div className="athlete-full"> <AthletesMenu/> <div className="athlete"> <header style={headerStyle}/> <div className="picture-container"> <img src={`/img/${athlete.image}`}/> <h2 className="name">{athlete.name}</h2> </div> <section className="description"> Olympic medalist from <strong><Flag {...athlete.country} showName="true"/></strong>, born in {athlete.birth} (Find out more on <a href={athlete.link} target="_blank">Wikipedia</a>). </section> <section className="medals"> <p>Winner of <strong>{athlete.medals.length}</strong> medals:</p> <ul>{ athlete.medals.map((medal, i) => <Medal key={i} {...medal}/>) }</ul> </section> </div> <div className="navigateBack"> <Link to="/">« Back to the index</Link> </div> </div> );};

export default AthletePage;

32

// src/components/AthletePage.js

// ...

const AthletePage = (props) => { const id = props.params.id; const athlete = athletes.find((athlete) => athlete.id === id); if (!athlete) { return <NotFoundPage/>; } const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( <div className="athlete-full"> <AthletesMenu/> <div className="athlete"> <header style={headerStyle}/> <div className="picture-container"> <img src={`/img/${athlete.image}`}/> <h2 className="name">{athlete.name}</h2> </div> // ...

33

// src/components/AthletePage.js

// ...

<section className="description"> Olympic medalist from <strong><Flag {...athlete.country} showName="true"/></strong>, born in {athlete.birth} (Find out more on <a href={athlete.link} target="_blank">Wikipedia</a>). </section> <section className="medals"> <p>Winner of <strong>{athlete.medals.length}</strong> medals:</p> <ul>{ athlete.medals.map((medal, i) => <Medal key={i} {...medal}/>) }</ul> </section> </div> <div className="navigateBack"> <Link to="/">« Back to the index</Link> </div> </div> );};

export default AthletePage;

34

AthletesMenu component

35

// src/components/AthletesMenu.js

import React from 'react';import { Link } from 'react-router';import athletes from '../data/athletes';

const AthletesMenu = (props) => ( <nav className="atheletes-menu"> {athletes.map(athlete => { return <Link key={athlete.id} to={`/athlete/${athlete.id}`} activeClassName="active"> {athlete.name} </Link>; })} </nav>);

export default AthletesMenu;

36

Flag component

37

// src/components/Flag.js

import React from 'react';

const Flag = (props) => ( <span className="flag"> <img className="icon" title={props.name} src={`/img/${props.icon}`}/> {props.showName && <span className="name"> {props.name}</span>} </span>);

export default Flag;

38

Medal component

39

// src/components/Medal.js

import React from 'react';

const medalTypes = { 'G': 'Gold', 'S': 'Silver', 'B': 'Bronze'};

const Medal = (props) => ( <li className="medal"> <span className={`symbol symbol-${props.type}`} title={medalTypes[props.type]}> {props.type} </span> <span className="year">{props.year}</span> <span className="city"> {props.city}</span> <span className="event"> ({props.event})</span> <span className="category"> {props.category}</span> </li>);

export default Medal;

40

NotFoundPage component// src/components/NotFoundPage.js

import React from 'react';import { Link } from 'react-router';

const NotFoundPage = (props) => ( <div className="not-found"> <h1>404</h1> <h2>Page not found!</h2> <p> <Link to="/">Go back to the main page</Link> </p> </div>);

export default NotFoundPage;

41

ROUTING

42

2 ROUTES

Index Page: /

Athlete Page: /athlete/:id

43

// 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 = ( <Route path="/" component={Layout}> <IndexRoute component={IndexPage}/> <Route path="athlete/:id" component={AthletePage}/> <Route path="*" component={NotFoundPage}/> </Route>);

export default routes;

44

// src/components/AppRoutes.js

import React from 'react';import { Router, hashHistory } from 'react-router';import routes from '../Routes';

const AppRoutes = (props) => ( <Router history={hashHistory} routes={routes} onUpdate={() => window.scrollTo(0, 0)}/>);

export default AppRoutes;

45

CLIENT APP

46

// src/app-client.js

import React from 'react';import ReactDOM from 'react-dom';import AppRoutes from './components/AppRoutes';

window.onload = () => { ReactDOM.render(<AppRoutes/>, document.getElementById('main'));};

47

HTML WRAPPER

48

// src/static/index.html

<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Judo Heroes - A Universal JavaScript demo application with React</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <div id="main"></div> <script src="/js/bundle.js"></script> </body></html>

49

BUILD CONFIG(BABEL + WEBPACK)

50

.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

LET'S BUILD IT!

52

// 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 assetsapp.use(Express.static(path.join(__dirname, 'static')));

// start the serverconst 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

READY... LET'S TEST IT

54

RECAPWhat 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

SERVER SIDERENDERING AND

ROUTING

56

Using browser history

57

// src/views/index.ejs

<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Judo Heroes - A Universal JavaScript demo application with React</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <div id="main"><%- markup -%></div> <script src="/js/bundle.js"></script> </body></html>

Converting static index.html into a template

58

Updating the server app

59

// ...// universal routing and renderingapp.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(<RouterContext {...renderProps}/>); } else { // otherwise we can render a 404 page markup = renderToString(<NotFoundPage/>); res.status(404); }

// render the index template with the embedded React markup return res.render('index', { markup }); } );}); 60

THAT'S IT!

LET'S TEST AGAIN

61

RECAPWhat 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

UNIVERSAL DATA RETRIEVAL

api-proxy & async-props(COMPLETE CHAPTER in )

UNIVERSAL STATE MANAGEMENT

Redux

Node.js Design Patterns

WHERE DO WE GOfrom here...

Code: https://github.com/lmammino/judo-heroes-263