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

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

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

A workshop to introduce Universal JavaScript with React (interactive version at http://loige.link/uni-js-workshop )

Luciano Mammino

September 03, 2017
Tweet

More Decks by Luciano Mammino

Other Decks in Technology

Transcript

  1. Universal JS Web Applications with React Luciano Mammino (@loige) Photo

    by Ryan Holloway on Unsplash loige.link/uni-js-workshop ROVINJ, Aug 30 2017
  2. Who is Luciano? Principal Application Engineer Connect Website - Twitter

    - GitHub - Linkedin fullstackbulletin.com -15% Print (NpBK15WSCR) -20% eBook (NeBK20WSCR)
  3. 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
  4. MOAR ADVANTAGES... Keep using React/JS paradigms for "static" websites Speed

    up content loading with linkprefetch loige.link/universal-react-made-easy-talk
  5. UNIVERSAL RENDERING Render the views of the application from the

    server (first request) and then in the browser (next requests)
  6. UNIVERSAL DATA RETRIEVAL Access data (and APIs) from both the

    server and the browser. AXIOS UNIVERSAL FETCH
  7. 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
  8. 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
  9. 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
  10. 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'));
  11. 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'));
  12. ✏ 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
  13. 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 )
  14. <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)
  15. 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'));
  16. 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'));
  17. ✏ 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
  18. 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'));
  19. 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'));
  20. ✏ 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
  21. 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'));
  22. 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'));
  23. 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'));
  24. ✏ 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
  25. 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'));
  26. 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
  27. 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
  28. Folders structure mkdir -p \ src/components \ src/data \ src/views

    \ static <- React components <- Data file <- Server templates <- Static assets (CSS, images)
  29. 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' }, }, ], }, };
  30. 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()] };
  31. 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>
  32. 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
  33. 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
  34. 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')); };
  35. 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!
  36. 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 ☕
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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}/>
  47. 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" />
  48. 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}/>
  49. 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
  50. 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}/>
  51. 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]}/>
  52. 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/>
  53. 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>
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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.
  60. ✏ 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
  61. 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
  62. 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
  63. 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')); };
  64. 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;
  65. 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;
  66. 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;
  67. 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;
  68. 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;
  69. 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!
  70. 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
  71. 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
  72. 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.
  73. 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
  74. 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
  75. 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' }, }, ], }, };
  76. How to report proper 404 status from the server? By

    using the rendering context! 16:55
  77. 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
  78. 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
  79. 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
  80. 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
  81. "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' }, }], }, };
  82. 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
  83. 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
  84. 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