Universal JavaScript Web Applications with React - Luciano Mammino - Codemotion Milan 2016
-
Upload
codemotion -
Category
Technology
-
view
368 -
download
0
Transcript of Universal JavaScript Web Applications with React - Luciano Mammino - Codemotion Milan 2016
WHO ISLUCIANO?
nodejsdesignpatterns.com
lmammino
loige
loige.co
2
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
ADVANTAGESOF UNIVERSAL JAVASCRIPT
"JavaScript-only" development
Maintainability
Better SEO
Faster "perceived" load time
6
MODULE SHARING
Use Node.js modules in the browser.
UMD
9
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
WHAT TOOLS AREWE GOING TO USE?
17
WHAT ARE WEGOING TO BUILD?
judo-heroes.herokuapp.com
bit.ly/judo-heroes-tutorial
18
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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
.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
// 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
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
// 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
// ...// 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
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
THANKS!
loige loige.colmammino
(Special thanks to , , Aleksandar Čambas & )@cirpo @andreaman87 @quasi_modal
64