Universal JS Web Applications with React Luciano Mammino (@loige) Photo by Ryan Holloway on Unsplash loige.link/uni-js-workshop ROVINJ, Aug 30 2017
Who is Luciano? Principal Application Engineer Connect Website - Twitter - GitHub - Linkedin fullstackbulletin.com -15% Print (NpBK15WSCR) -20% eBook (NeBK20WSCR)
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 13:50
ISOMORPHIC UNIVERSAL… what? loige.link/universal-js-story
NOT ONLY FOR THE WEB... Desktop applications Mobile applications Hardware!
ADVANTAGES OF UNIVERSAL JAVASCRIPT "JavaScript-only" development Maintainability Better SEO Faster "perceived" load time
MOAR ADVANTAGES... Keep using React/JS paradigms for "static" websites Speed up content loading with linkprefetch loige.link/universal-react-made-easy-talk
In the wild
Seems cool… but...
MODULE SHARING Use Node.js modules in the browser
UNIVERSAL RENDERING Render the views of the application from the server (first request) and then in the browser (next requests)
UNIVERSAL ROUTING Recognise the view associated to the current route from both the server and the browser.
UNIVERSAL DATA RETRIEVAL Access data (and APIs) from both the server and the browser. AXIOS UNIVERSAL FETCH
UNIVERSAL STATE Manage changes on the state tree both on the server and the client...
ALTERNATIVE JS?!
OK… let's stop complaining and start coding!
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 14:00
WHAT ARE WE GOING TO BUILD? loige.link/judo-heroes-app​ loige.link/judo-heroes-tutorial
Quick Demo
WHAT TOOLS ARE WE GOING TO USE?
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 14:05
A React primer ● Components ● JSX ● Composition ● Props ● Events & State
Everything is a component ● The view is made up by a tree of components (like the DOM is made up by a tree of HTML tags) ● Components can be created in different ways (pure functions, class syntax) ● A component can include other components as children ● Components content is defined in JSX ● Components can receive data from the outside (props) ● Components can manage internal data (state) ● Components can be installed (rendered) in an HTML page ● Generally you'll have one main component containing all the others in your page
Online React Sandbox codesandbox.io
Pure function syntax import React from 'react'; import { render } from 'react-dom'; const IAmAComponent = () => ( <div> <h1>This is a component as a pure function</h1> </div> ); render(<IAmAComponent />, document.getElementById('root'));
Class extend syntax import React from 'react'; import { render } from 'react-dom'; class IAmAnotherComponent extends React.Component { render() { return ( <div> <h1>This is a component created with ES6 classes</h1> </div> ) } } render(<IAmAnotherComponent />, document.getElementById('root'));
✏ Exercise - HelloWorld component Create an HelloWorld component that prints "Hello World" Render the HelloWorld component on the page https://codesandbox.io/s/p34lnpm6q0 14:20
JSX ● It looks like HTML/XML, considered a superset of JavaScript ● It can contain JavaScript expressions ● It is "transpiled" to plain JavaScript using Babel ● Can include regular HTML tags (they start with a lowercase character) ● … or other components (they start with an uppercase character) ● Can include regular HTML attributes … With some exceptions: ○ class becomes className ○ properties with hyphens becomes camelcase (background-color -> backgroundColor)
<div className="shopping-list"> <ul> <li>Mushrooms</li> <li>Lasagna</li> </ul> </div> React.createElement( "div", { className: "shopping-list" }, React.createElement( "ul", null, React.createElement( "li", null, "Mushrooms" ), React.createElement( "li", null, "Lasagna" ) ) ); JSX Compiled JS (By Babel/React Transpiler)
JSX examples import React from 'react'; import { render } from 'react-dom'; const SampleComponent = () => ( <h1> Last render time: <strong style={{backgroundColor: 'yellow', padding: '2px'}}> {(new Date).toISOString()} </strong> </h1> ) render(<SampleComponent />, document.getElementById('root'));
React components tree ParentComponent FirstChildComponent SecondChildComponent
Components Tree import React from 'react'; import { render } from 'react-dom'; const FirstChildComponent = () => ( <div style={{ background: 'peru', padding: '2px' }}> <h2>FirstChildComponent</h2> </div> ) const SecondChildComponent = () => ( <div style={{ background: 'aqua', padding: '2px' }}> <h2>SecondChildComponent</h2> </div> ) const ParentComponent = () => ( <div style={{ border: '2px dotted black', padding: '4px'}}> <h1>ParentComponent</h1> <FirstChildComponent/> <SecondChildComponent/> </div> ) render(<ParentComponent />, document.getElementById('root'));
✏ Exercise - Combine components Create a react app with 3 components ● Title component ● Content component ● Footer component Then create a component called Layout that contains all the 3 as shown aside Then render the Layout component on the page https://codesandbox.io/s/48ok2yv3z7 14:35
Props ● Attributes set to components are called Props ● They are used to pass "input" data into components (from the parent component) ● They allow to create "reusable" components import React from 'react'; import { render } from 'react-dom'; const PrintProps = (props) => ( <div> <h1>Received props</h1> <pre>{ JSON.stringify(props, null, 2) }</pre> </div> ) render(<PrintProps foo="bar" bar="baz" qoo="qoo" />, document.getElementById('root'));
Reusable components import React from 'react'; import { render } from 'react-dom'; const ChildComponent = (props) => ( <div style={{ background: props.color, padding: '2px' }}> <h2>{props.name}</h2> </div> ) const ParentComponent = () => ( <div style={{ border: '2px dotted black', padding: '4px'}}> <h1>ParentComponent</h1> <ChildComponent color="peru" name="FirstChildComponent"/> <ChildComponent color="aqua" name="SecondChildComponent"/> </div> ) render(<ParentComponent />, document.getElementById('root'));
✏ Exercise - HelloWorld component with props Create an HelloWorld component that prints "Hello NAME", where NAME is coming from a props called name Render the HelloWorld component on the page passing your name as prop https://codesandbox.io/s/5vmr4o2m2n 14:45
Decorator components import React from 'react'; import { render } from 'react-dom'; const Center = (props) => ( <div style={{ margin: 'auto', width: '50%', border: '2px solid #ccc', textAlign: 'center' }}> {props.children} </div> ) const HelloWorld = ({name = 'World'}) => (<h1>Hello {name}</h1>) const App = () => ( <Center> <HelloWorld name="Rovinj"/> </Center> ) render(<App />, document.getElementById('root'));
Iterations import React from 'react'; import { render } from 'react-dom'; const difficoultThings = [ 'Naming things', 'Cache invalidation', 'Off by one errors' ]; const App = () => ( <div> <h2>The 2 most difficoult things in IT</h2> <ol> {difficoultThings.map((thing) => ( <li>{thing}</li> ) )} </ol> </div> ); render(<App />, document.getElementById('root'));
Conditional rendering import React from 'react'; import { render } from 'react-dom'; const money = 220; const latestTransactions = [ { description: 'Restaurant', amount: 50 }, { description: 'Coffee', amount: 2 } ] const Transactions = ({data}) => { if (!data.length) { return (<div>No transaction available</div>) } return ( <div> <h3>Latest transactions</h3> <ul> { data.map((transaction) => ( <li>{transaction.amount} ({transaction.description})</li> )) } </ul> </div> ) } const App = () => ( <div> <h2>You have { money > 0 ? 'some': 'no' } Money!</h2> <p>Current Balance: {money}</p> <Transactions data={latestTransactions}/> </div> ); render(<App />, document.getElementById('root'));
✏ Exercise - Your favourite CSS colors Create a list of your favourite CSS colors. Hint: create a component to visualize a single color and render it multiple times based on the data contained in an array of colors. Bonus: Do something special with the color "RebeccaPurple". https://codesandbox.io/s/qqqz0n5z19 14:50
Events & state import React from 'react'; import { render } from 'react-dom'; const rnd = () => Math.round(Math.random() * 255) class RandomColor extends React.Component { constructor(props) { super(props); this.state = { color: props.startColor || 'red' }; } changeColor() { const color = `rgb(${rnd()}, ${rnd()}, ${rnd()})` this.setState({color}) } render() { return ( <div onClick={this.changeColor.bind(this)} style={{ padding: '80px', textAlign: 'center', background: this.state.color }} >Click me!</div> ) } } render(<RandomColor startColor="RebeccaPurple" />, document.getElementById('root'));
Quick Recap ● Everything is a component ● Composition over inheritance ● Pass application data through props ("data flows down") ● Components internal data constitutes state ● Components can react to events
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 15:00
Setup local dev environment mkdir judo-heroes-2 cd judo-heroes-2 npm init -y
Install dependencies npm i --save babel-cli@6.18.0 babel-core@6.18.2 babel-loader@7.1.2 babel-preset-es2015@6.18.0 babel-preset-react@6.16.0 ejs@2.5.2 express@5.0.0-alpha.5 react@15.4.2 react-dom@15.4.2 react-router-dom@4.0.0 webpack@2.7.0 webpack-dev-server@2.7.1
Folders structure mkdir -p src/components src/data src/views static <- React components <- Data file <- Server templates <- Static assets (CSS, images)
Babel Config .babelrc { "presets": ["react", "es2015"] }
Webpack config (webpack.config.js) const path = require('path'); module.exports = { entry: [ './src/app-client.js', ], output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', publicPath: '/static/', }, module: { rules: [ { test: path.join(__dirname, 'src'), use: { loader: 'babel-loader' }, }, ], }, };
Webpack config (webpack.config.js) - Add HMR ! const path = require('path'); const webpack = require('webpack'); module.exports = { entry: [ 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server', './src/app-client.js', ], output: {/* ... */}, devServer: { contentBase: path.join(__dirname, 'static'), historyApiFallback: true, port: 3000, hot: true }, module: {/* ... */}, plugins: [new webpack.HotModuleReplacementPlugin()] };
static/index.html (for development) <!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="/static/bundle.js"></script> </body> </html>
Add static resources (CSS and images) 1. Get a .zip with the needed static resources from here: loige.link/jhw-static 2. Unzip 3. Copy the content of the unzipped folder (static) into your static folder
Add data file (We will need it later) 1. Get a .zip with the needed data file from here: loige.link/jhw-data 2. Unzip 3. Copy the content of the unzipped folder (src) into your src folder
Temporary src/app-client.js (just to test our local setup!) import React from 'react'; import { render } from 'react-dom'; const AppClient = () => ( <h1>Hello Rovinj</h1> ); window.onload = () => { render(<AppClient />, document.getElementById('main')); };
Run dev server Run: node_modules/.bin/webpack-dev-server Now your project is available at http://localhost:3000 Try to change something in src/app-client.js and save!
dev env is finally ready!
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 15:15-15:30 ☕
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Break the app into components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
Flag component (src/components/Flag.js) import React from 'react'; export const Flag = props => ( <span className="flag"> <img className="icon" title={props.name} src={`/img/${props.icon}`} alt={`${props.name}'s flag`} /> {props.showName && <span className="name"> {props.name}</span>} </span> ); export default Flag; Props: ● name (e.g. "France") ● icon (e.g. "flag-fr.png") ● showName (true|false) <Flag name="France" icon="flag-fr.png" showName={true}/>
Medal component (src/components/Medal.js) import React from 'react'; export const medalTypes = { G: 'Gold', S: 'Silver', B: 'Bronze', }; export 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; Props: ● type ("G"|"S"|"B") ● year (e.g. "2017") ● city (e.g "Atlanta") ● event (e.g "Olympic Games") ● category (e.g "-86kg") <Medal type="G" year="2017" city="Atlanta" event="Olympic Games" category="-86kg" />
AthletesMenu component (src/components/AthletesMenu.js) import React from 'react'; const shortName = (fullname) => { const [name, surname] = fullname.split(' '); return `${name[0]}. ${surname}`; }; const AhtleteMenuLink = ({ to, label }) => ( <a href={to}>{label}</a> ); export const AthletesMenu = ({ athletes }) => ( <nav className="atheletes-menu"> { athletes.map(athlete => <AhtleteMenuLink key={athlete.id} to={`/athlete/${athlete.id}`} label={shortName(athlete.name)} /> )} </nav> ); export default AthletesMenu; Props: ● athletes (our data object) import athletes from './data/athletes' <AthletesMenu athletes={athletes}/>
AthleteCard component (src/components/AthleteCard.js) import React from 'react'; export const AthleteCard = props => ( <a href={`/athlete/${props.id}`}> <div className="athlete-preview"> <img src={`img/${props.image}`} alt={`${props.name}'s profile`} /> <h2 className="name">{props.name}</h2> <span className="medals-count"> <img src="/img/medal.png" alt="Medal icon" /> {props.medals.length} </span> </div> </a> ); export default AthleteCard; Props: id, image, name, medals (Attributes of an athlete in our data file) import athletes from './data/athletes' <AthleteCard {...athletes[0]}/> Spread syntax: Passes all the entries (key/values) of the athletes[0] object as props
IndexPage component (src/components/IndexPage.js) import React from 'react'; import { AthleteCard } from './AthleteCard'; export const IndexPage = ({ athletes }) => ( <div className="home"> <div className="athletes-selector"> {athletes.map( athleteData => <AthleteCard key={athleteData.id} {...athleteData} />, )} </div> </div> ); export default IndexPage; Props: ● athletes (our data object) import athletes from './data/athletes' <IndexPage athletes={athletes}/>
AthletePage component (src/components/AthletePage.js) import React from 'react'; import { AthletesMenu } from './AthletesMenu'; import { Medal } from './Medal'; import { Flag } from './Flag'; export const AthletePage = ({ athlete, athletes }) => { const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( <div className="athlete-full"> <AthletesMenu athletes={athletes} /> <div className="athlete"> <header style={headerStyle} /> <div className="picture-container"> <img alt={`${athlete.name}'s profile`} src={`/img/${athlete.image}`} /> <h2 className="name">{athlete.name}</h2> </div> <section className="description"> Olympic medalist from &nbsp;<strong><Flag {...athlete.country} showName="true" /></strong>, born in {athlete.birth} (Find out more on <a href={athlete.link}>Wikipedia</a>). </section> <section className="medals"> <p>Winner of <strong>{athlete.medals.length}</strong> medals:</p> <ul>{ athlete.medals.map(medal => <Medal key={medal.id} {...medal} />) }</ul> </section> </div> <div className="navigateBack"> <a href="/">« Back to the index</a> </div> </div> ); }; export default AthletePage; Props: ● athletes (our data object) ● athlete (the selected athlete) import athletes from './data/athletes' <AthletePage athletes={athletes} athlete={athletes[0]}/>
NotFoundPage component (src/components/NotFoundPage.js) import React from 'react'; export const NotFoundPage = () => ( <div className="not-found"> <h1>404</h1> <h2>Page not found!</h2> <p> <a href="/">Go back to the main page</a> </p> </div> ); export default NotFoundPage; Props: - <NotFoundPage/>
Layout component (src/components/Layout.js) import React from 'react'; export const Layout = props => ( <div className="app-container"> <header> <a href="/"> <img className="logo" src="/img/logo-judo-heroes.png" alt="Judo Heroes logo" /> </a> </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; Props: ● children (the element to render as main content) <Layout> <span>Your content here...</span> </Layout>
How do we assemble our components into a navigable prototype? We need routing!
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 15:45
React Router (v4) ● Dynamic Routing: Routing that takes place as your app is rendering ● Universal Routing: Can resolve routes also while rendering on the server ● Advanced features: ○ Nested Routes ○ Responsive Routes
import React from 'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Home = () => (<Page name="Home Page" />) const Page1 = () => (<Page name="Page 1" />) const Page2 = () => (<Page name="Page 2" />) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Route exact path="/" component={Home}/> <Route path="/pages/page1" component={Page1}/> <Route path="/pages/page2" component={Page2} /> </div> </Router> ) render(<App />, document.getElementById('root')); Router component wraps the app Route component allows to selectively render a component if the current URL matches the path Link component is used to create dynamic hyperlinks Base components
import React from 'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Route exact path="/" render={() => <Page name="Home Page" />}/> <Route path="/pages/page1" render={() => <Page name="Page 1" />}/> <Route path="/pages/page2" render={() => <Page name="Page 2" />}/> </div> </Router> ) render(<App />, document.getElementById('root')); Alternative syntax with render prop render prop syntax
import React from 'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Switch> <Route exact path="/" render={() => <Page name="Home Page" />}/> <Route path="/pages/page1" render={() => <Page name="Page 1" />}/> <Route path="/pages/page2" render={() => <Page name="Page 2" />}/> <Route render={() => <Page name="NOT FOUND!" />}/> </Switch> </div> </Router> ) render(<App />, document.getElementById('root')); Switch will render only the first route that match (or the last if none match) Switch and default route
import React from 'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Switch> <Route exact path="/" render={() => <Page name="Home Page" />}/> <Route path="/pages/:id" render={({match}) => <Page name={match.params.id} />}/> <Route render={() => <Page name="NOT FOUND!" />}/> </Switch> </div> </Router> ) render(<App />, document.getElementById('root')); Route parameters can be specified with :param syntax Parameterized routes Route components propagates the prop match to the child component. It contains all the params of the matched URL.
✏ Exercise - Routing Using React Router, implements a basic blog application with 2 routes: ● / (home) ● /post/:id (specific post) Bonus: Handle 404 pages https://codesandbox.io/s/42711k5xn0 Add react-router-dom as dependency 16:10
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 16:10
Let's define our routes ● IndexPage: / ● AthletePage: /athlete/:id ● NotFoundPage: Everything else
import React from 'react'; import { Route, Switch } from 'react-router-dom'; import { Layout } from './Layout'; import { IndexPage } from './IndexPage'; import { AthletePage } from './AthletePage'; import { NotFoundPage } from './NotFoundPage'; import athletes from '../data/athletes'; const renderIndex = () => <IndexPage athletes={athletes} />; const renderAthlete = ({ match, staticContext }) => { const id = match.params.id; const athlete = athletes.find(current => current.id === id); if (!athlete) { return <NotFoundPage staticContext={staticContext} />; } return <AthletePage athlete={athlete} athletes={athletes} />; }; export const App = () => ( <Layout> <Switch> <Route exact path="/" render={renderIndex} /> <Route exact path="/athlete/:id" render={renderAthlete} /> <Route component={NotFoundPage} /> </Switch> </Layout> ); export default App; Assemble client app src/components/App.js
Update src/app-client.js import React from 'react'; import { render } from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; import App from './components/App' const AppClient = () => ( <Router><App/></Router> ); window.onload = () => { render(<AppClient />, document.getElementById('main')); };
Let's test it
Clicking links makes the page refresh! We need to use the <Link> component! 16:20
src/components/AthleteCard.js import React from 'react'; export const AthleteCard = props => ( <a href={`/athlete/${props.id}`}> … </a> ); export default AthleteCard; import React from 'react'; import { Link } from 'react-router-dom'; export const AthleteCard = props => ( <Link to={`/athlete/${props.id}`}> … </Link> ); export default AthleteCard;
src/components/AthletePage.js import React from 'react'; export const AthletePage = ({ athlete, athletes }) => { const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( … <a href="/">« Back to the index</a> … ); }; export default AthletePage; import React from 'react'; import { Link } from 'react-router-dom'; export const AthletePage = ({ athlete, athletes }) => { const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( … <Link to="/">« Back to the index</Link> … ); }; export default AthletePage;
src/components/AthletesMenu.js import React from 'react'; … const AhtleteMenuLink = ({ to, label }) => ( <a href={to}>{label}</a> ); … export default AthletesMenu; import React from 'react'; import { Link } from 'react-router-dom'; … const AhtleteMenuLink = ({ to, label }) => ( <Link to={to}>{label}</Link> ); … export default AthletesMenu;
src/components/Layout.js import React from 'react'; export const Layout = props => ( <div className="app-container"> <header> <a href="/"> <img className="logo" src="/img/logo-judo-heroes.png" alt="Judo Heroes logo" /> </a> </header> … </div> ); export default Layout; import React from 'react'; import { Link } from 'react-router-dom'; export const Layout = props => ( <div className="app-container"> <header> <Link to="/"> <img className="logo" src="/img/logo-judo-heroes.png" alt="Judo Heroes logo" /> </Link> </header> … </div> ); export default Layout;
src/components/NotFoundPage.js import React from 'react'; export const NotFoundPage = () => ( <div className="not-found"> <h1>404</h1> <h2>Page not found!</h2> <p> <a href="/">Go back to the main page</a> </p> </div> ); export default NotFoundPage; import React from 'react'; import { Link } from 'react-router-dom'; export const NotFoundPage = () => ( <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;
Now everything should be fine! 16:30
Extra: mark the current menu item as active src/components/AthletesMenu.js import React from 'react'; import { Link } from 'react-router-dom'; … const AhtleteMenuLink = ({ to, label }) => ( <Link to={to}>{label}</Link> ); … export default AthletesMenu; import React from 'react'; import { Link, Route } from 'react-router-dom'; … const AhtleteMenuLink = ({ to, label }) => ( <Route path={to}> {({ match }) => ( <Link to={to} className={match ? 'active' : ''}> {label} </Link> )} </Route> ); … export default AthletesMenu;If we pass a function inside a Route we can render content. match will be true if the current path matches the route. This is active!
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 16:30
React Server Side Rendering (SSR) // src/testSSR.js import React from 'react'; import { renderToString } from 'react-dom/server'; const SampleApp = () => ( <h1>Hello World</h1> ); console.log(renderToString(<SampleApp/>)); node_modules/.bin/babel-node src/testSSR.js
Let's render our app // src/testSSR.js import React from 'react'; import { renderToString } from 'react-dom/server'; import { StaticRouter as Router } from 'react-router-dom'; import { App } from './components/App'; const ServerApp = () => ( <Router location="/" context={{}}> <App /> </Router> ); console.log(renderToString(<ServerApp/>)); StaticRouter is an implementation of React Router that accepts the location path as a prop.
Server Side Rendering and Routing We can create an Express server that can serve our pages with the React app already rendered (based on the current URL) We need a template first (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="/bundle.js"></script> </body> </html> This placeholder will be replaced with the markup rendered with React on the server side
src/server.js import path from 'path'; import { Server } from 'http'; import Express from 'express'; import React from 'react'; import { renderToString } from 'react-dom/server'; import { StaticRouter as Router } from 'react-router-dom'; import { App } from './components/App'; const app = new Express(); const server = new Server(app); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); app.use(Express.static(path.join(__dirname, '..', 'dist'))); app.use(Express.static(path.join(__dirname, '..', 'static'))); app.get('*', (req, res) => { const context = {}; const markup = renderToString( <Router location={req.url} context={context}> <App /> </Router>, ); return res.render('index', { markup }); }); server.listen(3000, () => { return console.info('Server running on http://localhost:3000'); }); Setup Express App with templating and static assets Universal routing and rendering. req.url is used to pass the current URL to React Router. The resulting markup is embedded into our template index.ejs and returned as response. Starts the server on the port 3000 16:45
Before we can start the server... 1. Delete static/index.html (or it will be served when we visit the home, skipping SSR) 2. Restore original webpack config (no HMR) 3. Run webpack to regenerate the bundle file: node_modules/.bin/webpack // webpack.config.js const path = require('path'); module.exports = { entry: [ './src/app-client.js', ], output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', publicPath: '/static/', }, module: { rules: [ { test: path.join(__dirname, 'src'), use: { loader: 'babel-loader' }, }, ], }, };
Start the server node_modules/.bin/babel-node src/server.js We use babel-node because we are rendering JSX and ES2015 modules in the server...
"BAD JUJU" for SEO!
How to report proper 404 status from the server? By using the rendering context! 16:55
src/components/NotFoundPage.js import React from 'react'; import { Link } from 'react-router-dom'; export const NotFoundPage = () => ( <div className="not-found"> <h1>404</h1> <h2>Page not found!</h2> <p> <Link href="/"> Go back to the main page </Link> </p> </div> ); export default NotFoundPage; import React from 'react'; import { Link } from 'react-router-dom'; export class NotFoundPage extends React.Component { componentWillMount() { const { staticContext } = this.props; if (staticContext) { staticContext.is404 = true; } } render() { return (<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; staticContext is available when rendering from StaticRouter and allows components to exchange arbitrary data will rendering
src/server.js // ... // universal routing and rendering app.get('*', (req, res) => { let status = 200; const context = {}; const markup = renderToString( <Router location={req.url} context={context}> <App /> </Router>, ); if (context.is404) { status = 404; } return res.status(status).render('index', { markup }); }); // ... context contains all the values that our components share during rendering with the StaticRouter
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build
Production build ● Our bundle needs to be minified ● React can be optimized too ● Babel-node is not good for production! Run Webpack for production: > webpack -p
"Webpacking" the server: webpack.server.config.js const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { target: 'node', node: { __dirname: false, }, externals: [nodeExternals({ modulesFromFile: true, })], entry: { js: './src/server.js', }, output: { path: path.join(__dirname, 'src'), filename: 'server-es5.js', libraryTarget: 'commonjs2', }, module: { rules: [{ test: path.join(__dirname, 'src'), use: { loader: 'babel-loader' }, }], }, };
Generating production files and start the app // install webpack utility to compile the server npm i webpack-node-externals // build the client node_modules/.bin/webpack -p // build the server node_modules/.bin/webpack -p --config webpack.server.config.js // start the server node src/server-es5.js
Agenda ● Introduction to Universal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Production build
Useful resources ● Full chapter in Node.Js design patterns about Universal JavaScript (remember the discount ) ● Create React App ● Universal Create React App ● Progressive Web Apps with React ● React/Redux Universal boilerplate with HMR ● The code for Judo Heroes (V2) - Remember to STAR this repo
Huge thanks to @andreaman87 and @quasi_modal (Follow them on Twitter!) FEEDBACK TIME https://joind.in/talk/85338

Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Workshop)

  • 1.
    Universal JS WebApplications with React Luciano Mammino (@loige) Photo by Ryan Holloway on Unsplash loige.link/uni-js-workshop ROVINJ, Aug 30 2017
  • 2.
    Who is Luciano? PrincipalApplication Engineer Connect Website - Twitter - GitHub - Linkedin fullstackbulletin.com -15% Print (NpBK15WSCR) -20% eBook (NeBK20WSCR)
  • 3.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 13:50
  • 4.
  • 5.
    NOT ONLY FOR THEWEB... Desktop applications Mobile applications Hardware!
  • 6.
    ADVANTAGES OF UNIVERSAL JAVASCRIPT "JavaScript-only"development Maintainability Better SEO Faster "perceived" load time
  • 7.
    MOAR ADVANTAGES... Keep using React/JSparadigms for "static" websites Speed up content loading with linkprefetch loige.link/universal-react-made-easy-talk
  • 8.
  • 9.
  • 10.
    MODULE SHARING Use Node.jsmodules in the browser
  • 11.
    UNIVERSAL RENDERING Render theviews of the application from the server (first request) and then in the browser (next requests)
  • 12.
    UNIVERSAL ROUTING Recognise theview associated to the current route from both the server and the browser.
  • 13.
    UNIVERSAL DATA RETRIEVAL Accessdata (and APIs) from both the server and the browser. AXIOS UNIVERSAL FETCH
  • 14.
    UNIVERSAL STATE Manage changeson the state tree both on the server and the client...
  • 15.
  • 17.
    OK… let's stopcomplaining and start coding!
  • 18.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 14:00
  • 19.
    WHAT ARE WEGOING TO BUILD? loige.link/judo-heroes-app​ loige.link/judo-heroes-tutorial
  • 24.
  • 25.
    WHAT TOOLS AREWE GOING TO USE?
  • 26.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 14:05
  • 27.
    A React primer ●Components ● JSX ● Composition ● Props ● Events & State
  • 28.
    Everything is acomponent ● The view is made up by a tree of components (like the DOM is made up by a tree of HTML tags) ● Components can be created in different ways (pure functions, class syntax) ● A component can include other components as children ● Components content is defined in JSX ● Components can receive data from the outside (props) ● Components can manage internal data (state) ● Components can be installed (rendered) in an HTML page ● Generally you'll have one main component containing all the others in your page
  • 29.
  • 30.
    Pure function syntax importReact from 'react'; import { render } from 'react-dom'; const IAmAComponent = () => ( <div> <h1>This is a component as a pure function</h1> </div> ); render(<IAmAComponent />, document.getElementById('root'));
  • 31.
    Class extend syntax importReact from 'react'; import { render } from 'react-dom'; class IAmAnotherComponent extends React.Component { render() { return ( <div> <h1>This is a component created with ES6 classes</h1> </div> ) } } render(<IAmAnotherComponent />, document.getElementById('root'));
  • 32.
    ✏ Exercise -HelloWorld component Create an HelloWorld component that prints "Hello World" Render the HelloWorld component on the page https://codesandbox.io/s/p34lnpm6q0 14:20
  • 33.
    JSX ● It lookslike HTML/XML, considered a superset of JavaScript ● It can contain JavaScript expressions ● It is "transpiled" to plain JavaScript using Babel ● Can include regular HTML tags (they start with a lowercase character) ● … or other components (they start with an uppercase character) ● Can include regular HTML attributes … With some exceptions: ○ class becomes className ○ properties with hyphens becomes camelcase (background-color -> backgroundColor)
  • 34.
    <div className="shopping-list"> <ul> <li>Mushrooms</li> <li>Lasagna</li> </ul> </div> React.createElement( "div", { className:"shopping-list" }, React.createElement( "ul", null, React.createElement( "li", null, "Mushrooms" ), React.createElement( "li", null, "Lasagna" ) ) ); JSX Compiled JS (By Babel/React Transpiler)
  • 35.
    JSX examples import Reactfrom 'react'; import { render } from 'react-dom'; const SampleComponent = () => ( <h1> Last render time: <strong style={{backgroundColor: 'yellow', padding: '2px'}}> {(new Date).toISOString()} </strong> </h1> ) render(<SampleComponent />, document.getElementById('root'));
  • 36.
  • 37.
    Components Tree import Reactfrom 'react'; import { render } from 'react-dom'; const FirstChildComponent = () => ( <div style={{ background: 'peru', padding: '2px' }}> <h2>FirstChildComponent</h2> </div> ) const SecondChildComponent = () => ( <div style={{ background: 'aqua', padding: '2px' }}> <h2>SecondChildComponent</h2> </div> ) const ParentComponent = () => ( <div style={{ border: '2px dotted black', padding: '4px'}}> <h1>ParentComponent</h1> <FirstChildComponent/> <SecondChildComponent/> </div> ) render(<ParentComponent />, document.getElementById('root'));
  • 38.
    ✏ Exercise -Combine components Create a react app with 3 components ● Title component ● Content component ● Footer component Then create a component called Layout that contains all the 3 as shown aside Then render the Layout component on the page https://codesandbox.io/s/48ok2yv3z7 14:35
  • 39.
    Props ● Attributes setto components are called Props ● They are used to pass "input" data into components (from the parent component) ● They allow to create "reusable" components import React from 'react'; import { render } from 'react-dom'; const PrintProps = (props) => ( <div> <h1>Received props</h1> <pre>{ JSON.stringify(props, null, 2) }</pre> </div> ) render(<PrintProps foo="bar" bar="baz" qoo="qoo" />, document.getElementById('root'));
  • 40.
    Reusable components import Reactfrom 'react'; import { render } from 'react-dom'; const ChildComponent = (props) => ( <div style={{ background: props.color, padding: '2px' }}> <h2>{props.name}</h2> </div> ) const ParentComponent = () => ( <div style={{ border: '2px dotted black', padding: '4px'}}> <h1>ParentComponent</h1> <ChildComponent color="peru" name="FirstChildComponent"/> <ChildComponent color="aqua" name="SecondChildComponent"/> </div> ) render(<ParentComponent />, document.getElementById('root'));
  • 41.
    ✏ Exercise -HelloWorld component with props Create an HelloWorld component that prints "Hello NAME", where NAME is coming from a props called name Render the HelloWorld component on the page passing your name as prop https://codesandbox.io/s/5vmr4o2m2n 14:45
  • 42.
    Decorator components import Reactfrom 'react'; import { render } from 'react-dom'; const Center = (props) => ( <div style={{ margin: 'auto', width: '50%', border: '2px solid #ccc', textAlign: 'center' }}> {props.children} </div> ) const HelloWorld = ({name = 'World'}) => (<h1>Hello {name}</h1>) const App = () => ( <Center> <HelloWorld name="Rovinj"/> </Center> ) render(<App />, document.getElementById('root'));
  • 43.
    Iterations import React from'react'; import { render } from 'react-dom'; const difficoultThings = [ 'Naming things', 'Cache invalidation', 'Off by one errors' ]; const App = () => ( <div> <h2>The 2 most difficoult things in IT</h2> <ol> {difficoultThings.map((thing) => ( <li>{thing}</li> ) )} </ol> </div> ); render(<App />, document.getElementById('root'));
  • 44.
    Conditional rendering import Reactfrom 'react'; import { render } from 'react-dom'; const money = 220; const latestTransactions = [ { description: 'Restaurant', amount: 50 }, { description: 'Coffee', amount: 2 } ] const Transactions = ({data}) => { if (!data.length) { return (<div>No transaction available</div>) } return ( <div> <h3>Latest transactions</h3> <ul> { data.map((transaction) => ( <li>{transaction.amount} ({transaction.description})</li> )) } </ul> </div> ) } const App = () => ( <div> <h2>You have { money > 0 ? 'some': 'no' } Money!</h2> <p>Current Balance: {money}</p> <Transactions data={latestTransactions}/> </div> ); render(<App />, document.getElementById('root'));
  • 45.
    ✏ Exercise -Your favourite CSS colors Create a list of your favourite CSS colors. Hint: create a component to visualize a single color and render it multiple times based on the data contained in an array of colors. Bonus: Do something special with the color "RebeccaPurple". https://codesandbox.io/s/qqqz0n5z19 14:50
  • 46.
    Events & state importReact from 'react'; import { render } from 'react-dom'; const rnd = () => Math.round(Math.random() * 255) class RandomColor extends React.Component { constructor(props) { super(props); this.state = { color: props.startColor || 'red' }; } changeColor() { const color = `rgb(${rnd()}, ${rnd()}, ${rnd()})` this.setState({color}) } render() { return ( <div onClick={this.changeColor.bind(this)} style={{ padding: '80px', textAlign: 'center', background: this.state.color }} >Click me!</div> ) } } render(<RandomColor startColor="RebeccaPurple" />, document.getElementById('root'));
  • 47.
    Quick Recap ● Everythingis a component ● Composition over inheritance ● Pass application data through props ("data flows down") ● Components internal data constitutes state ● Components can react to events
  • 48.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 15:00
  • 49.
    Setup local devenvironment mkdir judo-heroes-2 cd judo-heroes-2 npm init -y
  • 50.
    Install dependencies npm i--save babel-cli@6.18.0 babel-core@6.18.2 babel-loader@7.1.2 babel-preset-es2015@6.18.0 babel-preset-react@6.16.0 ejs@2.5.2 express@5.0.0-alpha.5 react@15.4.2 react-dom@15.4.2 react-router-dom@4.0.0 webpack@2.7.0 webpack-dev-server@2.7.1
  • 51.
    Folders structure mkdir -p src/components src/data src/views static <- React components <- Data file <- Server templates <- Static assets (CSS, images)
  • 52.
  • 53.
    Webpack config (webpack.config.js) constpath = require('path'); module.exports = { entry: [ './src/app-client.js', ], output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', publicPath: '/static/', }, module: { rules: [ { test: path.join(__dirname, 'src'), use: { loader: 'babel-loader' }, }, ], }, };
  • 54.
    Webpack config (webpack.config.js)- Add HMR ! const path = require('path'); const webpack = require('webpack'); module.exports = { entry: [ 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server', './src/app-client.js', ], output: {/* ... */}, devServer: { contentBase: path.join(__dirname, 'static'), historyApiFallback: true, port: 3000, hot: true }, module: {/* ... */}, plugins: [new webpack.HotModuleReplacementPlugin()] };
  • 55.
    static/index.html (for development) <!DOCTYPEhtml> <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="/static/bundle.js"></script> </body> </html>
  • 56.
    Add static resources(CSS and images) 1. Get a .zip with the needed static resources from here: loige.link/jhw-static 2. Unzip 3. Copy the content of the unzipped folder (static) into your static folder
  • 57.
    Add data file(We will need it later) 1. Get a .zip with the needed data file from here: loige.link/jhw-data 2. Unzip 3. Copy the content of the unzipped folder (src) into your src folder
  • 58.
    Temporary src/app-client.js (justto test our local setup!) import React from 'react'; import { render } from 'react-dom'; const AppClient = () => ( <h1>Hello Rovinj</h1> ); window.onload = () => { render(<AppClient />, document.getElementById('main')); };
  • 59.
    Run dev server Run: node_modules/.bin/webpack-dev-server Nowyour project is available at http://localhost:3000 Try to change something in src/app-client.js and save!
  • 60.
  • 61.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 15:15-15:30 ☕
  • 62.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 63.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 64.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 65.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 66.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 67.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 68.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 69.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 70.
    Break the appinto components Let's see all the components we will need for this app! ● Layout ● IndexPage ● AthletePage ● NotFoundPage ● AthleteCard ● AthletesMenu ● Flag ● Medal
  • 71.
    Flag component (src/components/Flag.js) importReact from 'react'; export const Flag = props => ( <span className="flag"> <img className="icon" title={props.name} src={`/img/${props.icon}`} alt={`${props.name}'s flag`} /> {props.showName && <span className="name"> {props.name}</span>} </span> ); export default Flag; Props: ● name (e.g. "France") ● icon (e.g. "flag-fr.png") ● showName (true|false) <Flag name="France" icon="flag-fr.png" showName={true}/>
  • 72.
    Medal component (src/components/Medal.js) importReact from 'react'; export const medalTypes = { G: 'Gold', S: 'Silver', B: 'Bronze', }; export 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; Props: ● type ("G"|"S"|"B") ● year (e.g. "2017") ● city (e.g "Atlanta") ● event (e.g "Olympic Games") ● category (e.g "-86kg") <Medal type="G" year="2017" city="Atlanta" event="Olympic Games" category="-86kg" />
  • 73.
    AthletesMenu component (src/components/AthletesMenu.js) importReact from 'react'; const shortName = (fullname) => { const [name, surname] = fullname.split(' '); return `${name[0]}. ${surname}`; }; const AhtleteMenuLink = ({ to, label }) => ( <a href={to}>{label}</a> ); export const AthletesMenu = ({ athletes }) => ( <nav className="atheletes-menu"> { athletes.map(athlete => <AhtleteMenuLink key={athlete.id} to={`/athlete/${athlete.id}`} label={shortName(athlete.name)} /> )} </nav> ); export default AthletesMenu; Props: ● athletes (our data object) import athletes from './data/athletes' <AthletesMenu athletes={athletes}/>
  • 74.
    AthleteCard component (src/components/AthleteCard.js) importReact from 'react'; export const AthleteCard = props => ( <a href={`/athlete/${props.id}`}> <div className="athlete-preview"> <img src={`img/${props.image}`} alt={`${props.name}'s profile`} /> <h2 className="name">{props.name}</h2> <span className="medals-count"> <img src="/img/medal.png" alt="Medal icon" /> {props.medals.length} </span> </div> </a> ); export default AthleteCard; Props: id, image, name, medals (Attributes of an athlete in our data file) import athletes from './data/athletes' <AthleteCard {...athletes[0]}/> Spread syntax: Passes all the entries (key/values) of the athletes[0] object as props
  • 75.
    IndexPage component (src/components/IndexPage.js) importReact from 'react'; import { AthleteCard } from './AthleteCard'; export const IndexPage = ({ athletes }) => ( <div className="home"> <div className="athletes-selector"> {athletes.map( athleteData => <AthleteCard key={athleteData.id} {...athleteData} />, )} </div> </div> ); export default IndexPage; Props: ● athletes (our data object) import athletes from './data/athletes' <IndexPage athletes={athletes}/>
  • 76.
    AthletePage component (src/components/AthletePage.js) importReact from 'react'; import { AthletesMenu } from './AthletesMenu'; import { Medal } from './Medal'; import { Flag } from './Flag'; export const AthletePage = ({ athlete, athletes }) => { const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( <div className="athlete-full"> <AthletesMenu athletes={athletes} /> <div className="athlete"> <header style={headerStyle} /> <div className="picture-container"> <img alt={`${athlete.name}'s profile`} src={`/img/${athlete.image}`} /> <h2 className="name">{athlete.name}</h2> </div> <section className="description"> Olympic medalist from &nbsp;<strong><Flag {...athlete.country} showName="true" /></strong>, born in {athlete.birth} (Find out more on <a href={athlete.link}>Wikipedia</a>). </section> <section className="medals"> <p>Winner of <strong>{athlete.medals.length}</strong> medals:</p> <ul>{ athlete.medals.map(medal => <Medal key={medal.id} {...medal} />) }</ul> </section> </div> <div className="navigateBack"> <a href="/">« Back to the index</a> </div> </div> ); }; export default AthletePage; Props: ● athletes (our data object) ● athlete (the selected athlete) import athletes from './data/athletes' <AthletePage athletes={athletes} athlete={athletes[0]}/>
  • 77.
    NotFoundPage component (src/components/NotFoundPage.js) importReact from 'react'; export const NotFoundPage = () => ( <div className="not-found"> <h1>404</h1> <h2>Page not found!</h2> <p> <a href="/">Go back to the main page</a> </p> </div> ); export default NotFoundPage; Props: - <NotFoundPage/>
  • 78.
    Layout component (src/components/Layout.js) importReact from 'react'; export const Layout = props => ( <div className="app-container"> <header> <a href="/"> <img className="logo" src="/img/logo-judo-heroes.png" alt="Judo Heroes logo" /> </a> </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; Props: ● children (the element to render as main content) <Layout> <span>Your content here...</span> </Layout>
  • 79.
    How do weassemble our components into a navigable prototype? We need routing!
  • 80.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 15:45
  • 81.
    React Router (v4) ●Dynamic Routing: Routing that takes place as your app is rendering ● Universal Routing: Can resolve routes also while rendering on the server ● Advanced features: ○ Nested Routes ○ Responsive Routes
  • 82.
    import React from'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Home = () => (<Page name="Home Page" />) const Page1 = () => (<Page name="Page 1" />) const Page2 = () => (<Page name="Page 2" />) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Route exact path="/" component={Home}/> <Route path="/pages/page1" component={Page1}/> <Route path="/pages/page2" component={Page2} /> </div> </Router> ) render(<App />, document.getElementById('root')); Router component wraps the app Route component allows to selectively render a component if the current URL matches the path Link component is used to create dynamic hyperlinks Base components
  • 83.
    import React from'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Route exact path="/" render={() => <Page name="Home Page" />}/> <Route path="/pages/page1" render={() => <Page name="Page 1" />}/> <Route path="/pages/page2" render={() => <Page name="Page 2" />}/> </div> </Router> ) render(<App />, document.getElementById('root')); Alternative syntax with render prop render prop syntax
  • 84.
    import React from'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Switch> <Route exact path="/" render={() => <Page name="Home Page" />}/> <Route path="/pages/page1" render={() => <Page name="Page 1" />}/> <Route path="/pages/page2" render={() => <Page name="Page 2" />}/> <Route render={() => <Page name="NOT FOUND!" />}/> </Switch> </div> </Router> ) render(<App />, document.getElementById('root')); Switch will render only the first route that match (or the last if none match) Switch and default route
  • 85.
    import React from'react' import { render } from 'react-dom'; import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom' const Page = ({name}) => (<div><h1>{name}</h1></div>) const Menu = () => ( <nav><ul> <li><Link to="/">Home</Link></li> <li><Link to="/pages/page1">Page1</Link></li> <li><Link to="/pages/page2">Page2</Link></li> </ul></nav> ) const App = () => ( <Router> <div> <Menu/> <hr /> <Switch> <Route exact path="/" render={() => <Page name="Home Page" />}/> <Route path="/pages/:id" render={({match}) => <Page name={match.params.id} />}/> <Route render={() => <Page name="NOT FOUND!" />}/> </Switch> </div> </Router> ) render(<App />, document.getElementById('root')); Route parameters can be specified with :param syntax Parameterized routes Route components propagates the prop match to the child component. It contains all the params of the matched URL.
  • 86.
    ✏ Exercise -Routing Using React Router, implements a basic blog application with 2 routes: ● / (home) ● /post/:id (specific post) Bonus: Handle 404 pages https://codesandbox.io/s/42711k5xn0 Add react-router-dom as dependency 16:10
  • 87.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 16:10
  • 88.
    Let's define ourroutes ● IndexPage: / ● AthletePage: /athlete/:id ● NotFoundPage: Everything else
  • 89.
    import React from'react'; import { Route, Switch } from 'react-router-dom'; import { Layout } from './Layout'; import { IndexPage } from './IndexPage'; import { AthletePage } from './AthletePage'; import { NotFoundPage } from './NotFoundPage'; import athletes from '../data/athletes'; const renderIndex = () => <IndexPage athletes={athletes} />; const renderAthlete = ({ match, staticContext }) => { const id = match.params.id; const athlete = athletes.find(current => current.id === id); if (!athlete) { return <NotFoundPage staticContext={staticContext} />; } return <AthletePage athlete={athlete} athletes={athletes} />; }; export const App = () => ( <Layout> <Switch> <Route exact path="/" render={renderIndex} /> <Route exact path="/athlete/:id" render={renderAthlete} /> <Route component={NotFoundPage} /> </Switch> </Layout> ); export default App; Assemble client app src/components/App.js
  • 90.
    Update src/app-client.js import Reactfrom 'react'; import { render } from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; import App from './components/App' const AppClient = () => ( <Router><App/></Router> ); window.onload = () => { render(<AppClient />, document.getElementById('main')); };
  • 91.
  • 92.
    Clicking links makesthe page refresh! We need to use the <Link> component! 16:20
  • 93.
    src/components/AthleteCard.js import React from'react'; export const AthleteCard = props => ( <a href={`/athlete/${props.id}`}> … </a> ); export default AthleteCard; import React from 'react'; import { Link } from 'react-router-dom'; export const AthleteCard = props => ( <Link to={`/athlete/${props.id}`}> … </Link> ); export default AthleteCard;
  • 94.
    src/components/AthletePage.js import React from'react'; export const AthletePage = ({ athlete, athletes }) => { const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( … <a href="/">« Back to the index</a> … ); }; export default AthletePage; import React from 'react'; import { Link } from 'react-router-dom'; export const AthletePage = ({ athlete, athletes }) => { const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` }; return ( … <Link to="/">« Back to the index</Link> … ); }; export default AthletePage;
  • 95.
    src/components/AthletesMenu.js import React from'react'; … const AhtleteMenuLink = ({ to, label }) => ( <a href={to}>{label}</a> ); … export default AthletesMenu; import React from 'react'; import { Link } from 'react-router-dom'; … const AhtleteMenuLink = ({ to, label }) => ( <Link to={to}>{label}</Link> ); … export default AthletesMenu;
  • 96.
    src/components/Layout.js import React from'react'; export const Layout = props => ( <div className="app-container"> <header> <a href="/"> <img className="logo" src="/img/logo-judo-heroes.png" alt="Judo Heroes logo" /> </a> </header> … </div> ); export default Layout; import React from 'react'; import { Link } from 'react-router-dom'; export const Layout = props => ( <div className="app-container"> <header> <Link to="/"> <img className="logo" src="/img/logo-judo-heroes.png" alt="Judo Heroes logo" /> </Link> </header> … </div> ); export default Layout;
  • 97.
    src/components/NotFoundPage.js import React from'react'; export const NotFoundPage = () => ( <div className="not-found"> <h1>404</h1> <h2>Page not found!</h2> <p> <a href="/">Go back to the main page</a> </p> </div> ); export default NotFoundPage; import React from 'react'; import { Link } from 'react-router-dom'; export const NotFoundPage = () => ( <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;
  • 98.
    Now everything shouldbe fine! 16:30
  • 99.
    Extra: mark thecurrent menu item as active src/components/AthletesMenu.js import React from 'react'; import { Link } from 'react-router-dom'; … const AhtleteMenuLink = ({ to, label }) => ( <Link to={to}>{label}</Link> ); … export default AthletesMenu; import React from 'react'; import { Link, Route } from 'react-router-dom'; … const AhtleteMenuLink = ({ to, label }) => ( <Route path={to}> {({ match }) => ( <Link to={to} className={match ? 'active' : ''}> {label} </Link> )} </Route> ); … export default AthletesMenu;If we pass a function inside a Route we can render content. match will be true if the current path matches the route. This is active!
  • 100.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build 16:30
  • 101.
    React Server SideRendering (SSR) // src/testSSR.js import React from 'react'; import { renderToString } from 'react-dom/server'; const SampleApp = () => ( <h1>Hello World</h1> ); console.log(renderToString(<SampleApp/>)); node_modules/.bin/babel-node src/testSSR.js
  • 102.
    Let's render ourapp // src/testSSR.js import React from 'react'; import { renderToString } from 'react-dom/server'; import { StaticRouter as Router } from 'react-router-dom'; import { App } from './components/App'; const ServerApp = () => ( <Router location="/" context={{}}> <App /> </Router> ); console.log(renderToString(<ServerApp/>)); StaticRouter is an implementation of React Router that accepts the location path as a prop.
  • 103.
    Server Side Renderingand Routing We can create an Express server that can serve our pages with the React app already rendered (based on the current URL) We need a template first (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="/bundle.js"></script> </body> </html> This placeholder will be replaced with the markup rendered with React on the server side
  • 104.
    src/server.js import path from'path'; import { Server } from 'http'; import Express from 'express'; import React from 'react'; import { renderToString } from 'react-dom/server'; import { StaticRouter as Router } from 'react-router-dom'; import { App } from './components/App'; const app = new Express(); const server = new Server(app); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); app.use(Express.static(path.join(__dirname, '..', 'dist'))); app.use(Express.static(path.join(__dirname, '..', 'static'))); app.get('*', (req, res) => { const context = {}; const markup = renderToString( <Router location={req.url} context={context}> <App /> </Router>, ); return res.render('index', { markup }); }); server.listen(3000, () => { return console.info('Server running on http://localhost:3000'); }); Setup Express App with templating and static assets Universal routing and rendering. req.url is used to pass the current URL to React Router. The resulting markup is embedded into our template index.ejs and returned as response. Starts the server on the port 3000 16:45
  • 105.
    Before we canstart the server... 1. Delete static/index.html (or it will be served when we visit the home, skipping SSR) 2. Restore original webpack config (no HMR) 3. Run webpack to regenerate the bundle file: node_modules/.bin/webpack // webpack.config.js const path = require('path'); module.exports = { entry: [ './src/app-client.js', ], output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', publicPath: '/static/', }, module: { rules: [ { test: path.join(__dirname, 'src'), use: { loader: 'babel-loader' }, }, ], }, };
  • 106.
    Start the server node_modules/.bin/babel-nodesrc/server.js We use babel-node because we are rendering JSX and ES2015 modules in the server...
  • 108.
  • 109.
    How to reportproper 404 status from the server? By using the rendering context! 16:55
  • 110.
    src/components/NotFoundPage.js import React from'react'; import { Link } from 'react-router-dom'; export const NotFoundPage = () => ( <div className="not-found"> <h1>404</h1> <h2>Page not found!</h2> <p> <Link href="/"> Go back to the main page </Link> </p> </div> ); export default NotFoundPage; import React from 'react'; import { Link } from 'react-router-dom'; export class NotFoundPage extends React.Component { componentWillMount() { const { staticContext } = this.props; if (staticContext) { staticContext.is404 = true; } } render() { return (<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; staticContext is available when rendering from StaticRouter and allows components to exchange arbitrary data will rendering
  • 111.
    src/server.js // ... // universalrouting and rendering app.get('*', (req, res) => { let status = 200; const context = {}; const markup = renderToString( <Router location={req.url} context={context}> <App /> </Router>, ); if (context.is404) { status = 404; } return res.status(status).render('index', { markup }); }); // ... context contains all the values that our components share during rendering with the StaticRouter
  • 113.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Extra: Production build
  • 114.
    Production build ● Ourbundle needs to be minified ● React can be optimized too ● Babel-node is not good for production! Run Webpack for production: > webpack -p
  • 115.
    "Webpacking" the server: webpack.server.config.js constpath = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { target: 'node', node: { __dirname: false, }, externals: [nodeExternals({ modulesFromFile: true, })], entry: { js: './src/server.js', }, output: { path: path.join(__dirname, 'src'), filename: 'server-es5.js', libraryTarget: 'commonjs2', }, module: { rules: [{ test: path.join(__dirname, 'src'), use: { loader: 'babel-loader' }, }], }, };
  • 116.
    Generating production filesand start the app // install webpack utility to compile the server npm i webpack-node-externals // build the client node_modules/.bin/webpack -p // build the server node_modules/.bin/webpack -p --config webpack.server.config.js // start the server node src/server-es5.js
  • 117.
    Agenda ● Introduction toUniversal JavaScript ● Today's app "preview" ● A React primer ● Setup local development environment ● Break the app into components ● React Router primer ● Add routing to our app ● Make the app universal ● Production build
  • 118.
    Useful resources ● Fullchapter in Node.Js design patterns about Universal JavaScript (remember the discount ) ● Create React App ● Universal Create React App ● Progressive Web Apps with React ● React/Redux Universal boilerplate with HMR ● The code for Judo Heroes (V2) - Remember to STAR this repo
  • 119.
    Huge thanks to@andreaman87 and @quasi_modal (Follow them on Twitter!) FEEDBACK TIME https://joind.in/talk/85338