Agnieszka Andrzejewska - BIM School Course in Kraków
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Workshop)
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
7. MOAR
ADVANTAGES...
Keep using React/JS paradigms for "static" websites
Speed up content loading with linkprefetch
loige.link/universal-react-made-easy-talk
18. 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
19. WHAT ARE WE GOING TO BUILD?
loige.link/judo-heroes-app
loige.link/judo-heroes-tutorial
26. 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
27. A React primer
● Components
● JSX
● Composition
● Props
● Events & State
28. 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
30. 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'));
31. 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'));
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 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)
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 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'));
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
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
47. 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
48. 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
49. Setup local dev environment
mkdir judo-heroes-2
cd judo-heroes-2
npm init -y
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 (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'));
};
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
15:15-15:30
☕
62. 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
63. 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
64. 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
65. 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
66. 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
67. 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
68. 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
69. 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
70. 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
76. 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
<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)
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/>
78. 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>
79. How do we assemble our components
into a navigable prototype?
We need routing!
80. 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
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
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 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
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;
99. 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!
100. 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
101. 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
102. 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.
103. 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
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 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' },
},
],
},
};
109. How to report proper 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
// ...
// 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
112.
113. 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
114. 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
116. 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
117. 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
118. 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
119. Huge thanks to @andreaman87 and @quasi_modal (Follow them on Twitter!)
FEEDBACK TIME
https://joind.in/talk/85338