<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Add React in One Minute</title>
</head>
<body>
<h2>Add React Blog in One Minute</h2>
<p>This page demonstrates using React with no build tooling.</p>
<p>React is loaded as a script tag.</p>
<!-- We will put our React component inside this div. -->
<div id="react_app_root"></div>
<!-- Load React. -->
<!-- Note: when deploying, replace "development.js" with "production.min.js". -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- Load our React component. -->
<script src="blog.js"></script>
</body>
</html>
index.html
'use strict';
const title = 'Hey, I have just joined React pro-camp';
const element = React.createElement(
'div',
{style: {color: 'darkorange'}},
this.state.post.title
);
const reactRootDomNode = document.querySelector('#react_app_root');
ReactDOM.render(element, reactRootDomNode);
blog.js
import React from 'react';
import ReactDOM from 'react-dom';
const title = 'Hey, I have just joined React pro-camp';
const element = React.createElement(
'div',
{style: {color: 'darkorange'}},
this.state.post.title
);
const reactRootDomNode = document.querySelector('#react_app_root');
ReactDOM.render(element, reactRootDomNode);
blog.js
<!DOCTYPE html>
<html>
...
<body>
...
<div id="react_app_root"></div>
...
<!-- Load React and our component. -->
<script src="blog.js" type="module"></script>
</body>
</html>
index.html
import React from 'react';
import ReactDOM from 'react-dom';
const title = 'Hey, I have just joined React pro-camp';
const element = React.createElement(
'div',
{style: {color: 'darkorange'}},
this.state.post.title
);
const reactRootDomNode = document.querySelector('#react_app_root');
ReactDOM.render(element, reactRootDomNode);
blog.js
<script src="bundle.js"></script>
index.html
blog.js
Webpack
Rollup
Parcel, etc...
bundle.js (react, react-dom, blog.js)
import React from 'react';
import ReactDOM from 'react-dom';
const title = 'Hey, I have just joined React pro-camp';
const element = React.createElement(
'div',
{style: {color: 'darkorange'}},
this.state.post.title
);
const reactRootDomNode = document.querySelector('#react_app_root');
ReactDOM.render(element, reactRootDomNode);
index.js
>>> npx create-react-app my-blog
>>> cd my_blog
>>> npm run build
const element = React.createElement(
'div', {style: {color: 'darkorange'}}, 'My First Post'
);
import React from 'react';
import ReactDOM from 'react-dom';
const element = <div style={{color: 'darkorange'}}>'My First Post'</div>
const reactRootDomNode = document.querySelector('#react_app_root');
ReactDOM.render(element, reactRootDomNode);
HTML tag
(or React Component)
HTML attributes
(or React properties)
Content (children)
const element = <div>My First Post</div>;
const element = <div id="post-title">My First Post</div>;
const element = (<div id="post-title">
My First Post
</div>);
const element = (<div id="post-title">
<h1>My First Post</h1>
<p>Hey, I have just started React pro-camp</p>
</div>);
const user = { firstName: 'Viktor', lastName: 'Shevchenko'};
function formatName(user) {
return user.firstName + user.lastName;
}
const element = <h1>Hello, {formatName(user)}</h1>;
Use {variableName} to access data from scope
In order to code with JSX syntax become a valid JavaScript code it has to be tranformed.
JSX
Transformer
TypeScript
Babel
const element = <div>My First Post</div>;
const element = React.CreateElement('div', null, 'My First Post');
const element = <div id="post-title">My First Post</div>;;
const element = React.CreateElement('div', {id: 'post-title'}, 'My First Post');
const element = (<div id="post-title">
<h1>My First Post</h1>
<p>Hey, I have just started React pro-camp</p>
</div>);
const element = React.CreateElement('div', {id: 'post-title'},
React.CreateElement('h1', null, 'My First Post')
);
const element = (<div id="post-title">
<h1>My First Post</h1>
</div>);
const element = React.CreateElement('div', {id: 'post-title'},
React.CreateElement('h1', null, 'My First Post')
);
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Greetitg name="Viktor" />;
// the same as
const element = React.createElement(Greeting, {name: 'Viktor'}, null)
ReactDOM.render(element, document.getElementById('root'));
const element = {
type: Greeting,
props: {
children: {
type: 'h1',
props: {
children: 'Hello Viktor'
}
}
}
}
class Randomizer extends React.Component {
constructor(props) {
super(props);
this.state = {count: Math.rand()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>Value is - {this.state.count}.</h2>
</div>
);
}
}
ReactDOM.render(<Randomizer />, document.getElementById('root'));
// some time later
ReactDOM.render(<Randomizer />, document.getElementById('root'));
class Randomizer extends React.Component {
constructor(props) {
super(props);
this.state = {count: Math.rand()};
}
reset(){
this.setState({count: 0});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>Value is - {this.state.count}.</h2>
<button onClick={this.reset}>Reset</button>
</div>
);
}
}
this?
class Randomizer extends React.Component {
constructor(props) {
super(props);
this.state = {count: Math.rand()};
this.reset = this.reset.bind(this);
}
reset(e){
this.setState({count: 0});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>Value is - {this.state.count}.</h2>
<button onClick={(e) => this.reset(e)}>Reset</button>
</div>
);
}
}
OR
class Randomizer extends React.Component {
constructor(props) {
super(props);
this.reset = this.reset.bind(this);
}
static getDerivedStateFromProps(props, state){
// warning. Bad example.
return !this.state.count && {count: props.count}
}
componentDidMount(){
this.interval = this.setInterval(
() => this.setState({count: Math.rand()}),
1000
);
}
componentWillUnmount(){
clearInterval(this.interval);
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>Value is - {this.state.count}.</h2>
<button onClick={this.reset}>Reset</button>
</div>
);
}
}
class Randomizer extends React.Component {
constructor(props) {
super(props);
this.reset = this.reset.bind(this);
}
shouldComponentUpdate(nextProps, nextState){
if (nextProps.count === this.props.count) {
return false;
}
}
getSnapshotBeforeUpdate(prevProps, prevState) {
}
componentDidUpdate(prevProps, prevState, snapshot) {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>Value is - {this.state.count}.</h2>
<button onClick={this.reset}>Reset</button>
</div>
);
}
}
class Randomizer extends React.Component {
constructor(props) {
super(props);
this.state = {hasError: false}
}
static getDerivedStateFromError(error) {
// Update component state
return {hasError: true}
}
componentDidCatch(error, info) {
// side effect, log error.
}
render() {
if(this.state.hasError) {
return <h2>Error<h2>
}
return (
<div>
<h1>Hello, world!</h1>
<h2>Value is - {this.state.count}.</h2>
<button onClick={this.reset}>Reset</button>
</div>
);
}
}
React Only Updates What’s Necessary
Reconciliation
Create React App
Select theme
Deploy app
const MY_FILMS = [
{
name: 'Mad Max: Road of Fury',
description: 'In a post-apocalyptic wasteland, a woman rebels against a
tyrannical ruler in search for her homeland with the aid of a group
of female prisoners, a psychotic worshiper, and a drifter named Max.',
rating: 8.1
tags: ['action', 'apocalyptic', 'post apocalypse', 'desert', 'australia', 'female warrior', 'chase']
},
{
name: 'Logan',
description: 'In a future where mutants are nearly extinct, an elderly and
weary Logan leads a quiet life. But when Laura, a mutant child pursued
by scientists, comes to him for help, he must get her to safety.',
rating: 7.9
tags: ['marvel comics', 'x men', 'superhero', 'mutant', 'reluctant hero']
}
]
Lets build a list of elements based on array
render() {
<div>
{MY_FILMS.map(film => <div>{film.name}</div>)}
</div>
}
Use ".map" Array method to render a list
Array index
Unique id
<li key={0}>Mad Max</li>
<li key={1}>Logan</li>
<li key={1002}>Mad Max</li>
<li key={1003}>Logan</li>
this.setState([{name: 'Avangers'}, ...this.state.films]);
<li key={0}>Avangers</li>
<li key={1}>Mad Max</li>
<li key={2}>Logan</li>
<li key={1001}>Avangers</li>
<li key={1002}>Mad Max</li>
<li key={1003}>Logan</li>
keys are new!
keys are same!
li[0].textContent = 'Avangers'; // replace 'Mad Max'
li[0].textContent = 'Mad Max'; // replace 'Logan'
var li3 = document.createElement('li');
li3.textContent = 'Logan';
li.parentNode.appendChild(li3)
var li0 = document.createElement('li');
li0.textContent = 'Avangers';
li.parentNode.prependChild(li0)
function Rating ({hasVouted}) {
if (hasVouted) {
return <div>You have already rated this movie</div>
}
return (
<div>Please rate this movie</div>
)
}
class Rating extends React.Component {
render() {
if (hasVouted) {
return <div>You have already rated this movie</div>
}
return (
<div>Please rate this movie</div>
)
}
}
<Rating hasVouted={true} />
function Rating ({hasVouted}) {
const RatingLabel = hasVouted ? <div>You have already rated this movie</div> : <div>Please rate this movie</div>
return (
<RatingLabel />
)
}
function Rating ({hasVouted}) {
return (
{!hasVouted && <div>Please rate this movie</div>}
)
}
function Rating ({hasVouted}) {
return (
{hasVouted ? <div>You have already rated this movie</div> : <div>Please rate this movie</div>}
)
}
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>
Implement selection on film when clicking on its name.
Display Selected Field Section only if any film is selected
Display description of selected film
Reimplement Selected movie to Edit Movie
How to avoid changes in the original component?
Task: Rebuild with uncontrolled components
3. Callback Functions4. Event Bubbling
Child:
Child.contextTypes = {
color: PropTypes.string
};
Parent:
getChildContext() {
return {color: "purple"};
}
Parent.childContextTypes = {
color: PropTypes.string
};
techniques in React
class Tag extends React.Component {
render() {
return <pre>{this.props.tag}</pre>;
}
}
{tags.map(tag => (
<Tag key={tag} tag={tag} />
))}
function EmojiBlock(props) {
return <span>{`🔥`}{props.children}{`🔥`}</span>
}
{tags.map(tag => (
<EmojiBlock key={tag}><Tag tag={tag}/></EmojiBlock>
))}
class Tag extends React.Component {
render() {
const {emoji, tag} = this.props;
return <pre>{emoji}{tag}{emoji}</pre>;
}
}
{tags.map(tag => (
<Tag key={tag} tag={tag}/>
))}
function EmojiTag(props) {
return <Tag {...props} />;
}
{tags.map(tag => (
<EmojiTag key={tag} tag={tag} emoji={`❤️`} />
))}
import React from "react";
export default class Emoji extends React.Component {
constructor(props) {
super(props);
this.emojiList = ["🤟", "🔥", "⭐️", "🦄"];
this.state = {
emojiIdx: 0
};
}
rotateEmoji = () => {
const nextIndex = this.state.emojiIdx + 1;
this.setState({
emojiIdx: nextIndex === this.emojiList.length ? 0 : nextIndex
});
};
render() {
null;
}
}
class EmojiTagInherited extends Emoji {
render() {
const emoji = this.emojiList[this.state.emojiIdx];
return (
<pre onClick={this.rotateEmoji}>
{emoji}
{this.props.tag}
{emoji}
</pre>
);
}
}
{tags.map(tag => (
<EmojiTagInherited key={tag} tag={tag} />
))}
Pros
Cons
export default function withEmoji(WrappedComponent) {
return class WithEmoji extends React.Component {
constructor(props) {
super(props);
this.emojiList = ["🤟", "🔥", "⭐️", "🦄"];
this.state = {
emojiIdx: 0
};
}
rotateEmoji = () => {
const nextIndex = this.state.emojiIdx + 1;
this.setState({
emojiIdx: nextIndex === this.emojiList.length ? 0 : nextIndex
});
};
render() {
return (
<WrappedComponent
emoji={this.emojiList[this.state.emojiIdx]}
rotateEmoji={this.rotateEmoji}
{...this.props}
/>
);
}
};
}
const TagWithEmoji = withEmoji(EmojiTag);
{tags.map(tag => (
<TagWithEmoji key={tag} tag={tag} />
))}
{tags.map(tag => (
<EmojiTag key={tag} tag={tag} />
))}
function EmojiTag({ tag, emoji = null, rotateEmoji = () => {} }) {
return (
<pre onClick={rotateEmoji}>
{emoji}
{tag}
{emoji}
</pre>
);
}
Mutation of the original component
class Film extends React.Component {
render() {
return (
<div>
Name: {this.props.name},
Description: {this.props.description}
</div>
);
}
}
function withLogProps(Component) {
InputComponent.prototype.componentDidUpdate = function(prevProps) {
console.log('Previous props: ', prevProps);
console.log('Current props: ', this.props);
};
return Component;
}
const FilmLogger = withLogProps(Film);
function logProps(Component) {
return class extends React.Component {
componentDidUpdate(prevProps) {
console.log('Previous props: ', prevProps);
console.log('Current props: ', this.props);
}
render() {
return <WrappedComponent />;
}
}
}
return <WrappedComponent {...this.props} />;
Always bypass unrelated props
Maximizing Composability
const FilmLogger = withDebugLogProps(Film);
const FilmLogger = withLogProps(Film, 'debug');
const FilmLogger = withLogProps('debug')(Film);
HOC in render method
class FilmLogger extends React.Component {
render() {
const FilmWithLogging = withLogProps(Film);
return (
<FilmWithLogging />
)
}
}
HOC copy static methods
class Film extends React.Component {
render() {
return (
<div>
Name: {this.props.name},
Description: {this.props.description}
</div>
);
}
}
Film.defaultProps = {
name: 'Terminator',
descriptions: 'Cyborg from future ..... '
}
const FilmLogger = withLogProps(Film);
FilmLogger.defaultProps ???
HOC does not pass ref
class Film extends React.Component {
render() {
return (
<div ref={this.props.ref}>
Name: {this.props.name},
Description: {this.props.description}
</div>
);
}
}
const FilmLogger = withLogProps(Film);
...
<FilmLogger ref={c => this.container = c}>
Pros
Cons
class Emoji extends React.Component {
constructor(props) {
super(props);
this.emojiList = ["🤟", "🔥", "⭐️", "🦄"];
this.state = {
emojiIdx: 0
};
}
rotateEmoji = () => {
const nextIndex = this.state.emojiIdx + 1;
this.setState({
emojiIdx: nextIndex === this.emojiList.length ? 0 : nextIndex
});
};
render() {
return this.props.render({
emoji: this.emojiList[this.state.emojiIdx],
rotateEmoji: this.rotateEmoji
});
}
}
function EmojiTag({ tag, emoji = null, rotateEmoji = () => {} }) {
return (
<pre onClick={rotateEmoji}>
{emoji}
{tag}
{emoji}
</pre>
);
}
{tags.map(tag => (
<EmojiRP key={tag} render={props => <EmojiTag tag={tag} {...props} />} />
))}
{tags.map(tag => (
<EmojiRP key={tag} render={props => <EmojiTag tag={tag} />} />
))}
/* props = {emoji, rotateEmoji} */
Pros
Cons
История
Решения
Нужна ли url? EE задачи
Problem
Основная идея - не менять принцип работы браузера - а дать API для управления url
Navigation
window.history.back();
window.history.go(-1);
window.history.forward();
window.history.go(1);
var numberOfEntries = window.history.length;
Manipulation
window.history.pushState({data: 'test'}, 'page 2', page2.html);
window.history.replaceState({data: 'test'}, 'page 2', page2.html);
window '
(window.onpopstate = funcRef;)
State - The state object is a JavaScript object which is associated with the new history entry
Title - title for the state to which you're moving
URL - The new history entry's URL. Understand when the browser does any server request
pushState() never causes a "hashchange" event to be fired, even if the new URL differs from the old URL only in its hash.
Static
Dynamic
React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI
Catch
Do Not Catch
Render a fallback UI
Log error information
What is
class ListOfInts extends React.PureComponent {
render() {
return <div>{this.props.ints.join(",")}</div>;
}
}
const ListOfInts = React.memo(props => <div>{this.props.ints.join(",")}</div>)
const ListOfInts = React.memo(props => <div>{this.props.ints.join(",")}</div>, areEqualFunction)
*areEqualFunction - should return reversed from shouldComponentUpdate
CommonJS
AMD
Ecma2015
Techniques for importing code in JS?
import('someOtherModule.js')
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
// Film.jsx
const Film = () => <div>Terminator</div>;
export default Film;
// App.jsx
import React, { lazy } from 'react';
const Film = lazy(() => import('./Film'));
class App extends React.Component {
...
render() {
return(
<div>
<h1>App with Films<h1>
<Film>
</div>
);
}
}
// App.jsx
import React, { lazy, Suspense } from 'react';
const Film = lazy(() => import('./Film'));
class App extends React.Component {
...
render() {
return(
<div>
<h1>App with Films<h1>
<Suspense fallback={<h2>Films are loading<h2>}>
<Film />
</Suspense>
</div>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
ReactDOM.render(<App />, document.getElementById('root'));
<Suspense fallback={<h2>Films are loading<h2>} maxDuration='500'>
<Film />
</Suspense>
class Suspense extends React.Component {
constructor(props) {
super(props);
this.state = {
showFallbackUI: true;
}
}
componentDidCatch(error) {
if (error instanceOf Promise) {
error.then(() => this.setState({showFallbackUI: false}))
}
}
render() {
return this.state.showFallbackUI ? this.props.fallback : this.props.children;
}
}
class RandomInt extends React.Component {
constructor(props) {
super(props);
this.state = {
ints: [5, 3, 8]
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
ints: [...this.state.ints,
parseInt(Math.random() * 10, 10)
];
});
}
render() {
return (
<div>
<button
onClick={this.handleClick}
>
Add Random Int
</button>
<ListOfInts ints={this.state.ints} />
</div>
);
}
}
import { useState } from 'react';
function RandomInt() {
const [ints, setInts] = useState([]);
return(
<div>
<button
onClick={setInts([...ints,
parseInt(Math.random() * 10, 10)])}
>
Add Random Int
</button>
<ListOfInts ints={ints} />
</div>
)
}
Custom
a function that contains a stateful logic and is used in React functional component. (starts with 'use')
useEffect(() => {
googleMap.panTo(props.coordinates)
});
componentDidMount
componentDidUpdate
useEffect(() => {
document.addEventListener('click', handleDocumentClick);
return () => {
document.removeEventListener('click', handleDocumentClick)
}
});
componentDidMount
componentDidUpdate
componentWillUnmount
useEffect(() => {
document.addEventListener(props.event, handleDocumentClick);
return () => {
document.removeEventListener(props.event, handleDocumentClick)
}
}, [props.event]);
componentDidMount
conditional componentDidUpdate
componentWillUnmount
useEffect(() => {
document.addEventListener(props.event, handleDocumentClick);
return () => {
document.removeEventListener(props.event, handleDocumentClick)
}
}, []);
componentDidMount
componentWillUnmount
function Link(props) {
const router = useContext(RouterContext);
return() {
<a
href=""
onClick={e => {
e.preventDefault();
router.pushState({}, "", props.to);
}}
>
{props.children}
</a>
}
}
export default class Link extends React.Component {
render() {
return (
<RouterContext.Consumer>
{router => (
<a
href=""
onClick={e => {
e.preventDefault();
router.pushState({}, "", this.props.to);
}}
>
{this.props.children}
</a>
)}
</RouterContext.Consumer>
);
}
}
function Map() {
const googleMap = useRef(null);
render() {
return (
<Map
item
xs={12}
style={style}
google={google}
onReady={(gm) => googleMap.current = gm }
onClick={(e) => googleMap.current.panTo(e.coordinates)}
zoom={10}
/>
)
}
}
class Map extends React.Component {
ready(googleMap) {
this.map = googleMap;
}
onMapClick(e) {
this.googleMap.panTo(e.coordinates);
}
render() {
return (
<Map
item
xs={12}
style={style}
google={google}
onReady={this.ready}
onClick={this.onMapClick}
zoom={10}
/>
)
}
}