Basic Tutorial of React for Programmers
-
Upload
david-rodenas -
Category
Software
-
view
25 -
download
1
Transcript of Basic Tutorial of React for Programmers
Basic ReactJS for Programmers
by Dr. David Rodenas
Origin
@drpicox
FB about MVC
3
https://youtu.be/nYkdrAPrdcw?t=10m20s
2014
@drpicox
FB about MVC
4
“MVC works pretty well for small applications…
but it doesn’t make room for new features.”
“Flux is a single direction data flow, that avoids all the arrows
going on all directions what makes really
hard to understand the system.”
@drpicox
FB about MVC
5
“Let’s see a real good example: FB chat”
“How we get to the point, so we were annoying our users so much the just they wanted us to fix chat?”
“The problems here were: • The code has no structure • It was very imperative, that makes it fragile • It loose a lot of the original intend behind it, its hard to tell what it tries [to do] • Add more features only gets this code larger • We had our most annoying chat bug happen over and over • We were always fixing some pretty good edge case, the whole system was fragile • … • This code becomes more fragile with the time. • No member of the team wanted to touch it, they wanted to jump to any other bug.”
@drpicox
FB about MVC
6
f lux
@drpicox
FB about Rendering
7
“Imperative Rendering”“If renders all each time the screen flickers”
“We wanted always render all, no matter what”“Here is where React comes in.”
ReactJS
@drpicox
Prerequisite
Install React Developer Tools
Basics
@drpicox
Hello World<!-- hello-world.html --> <div id="root"></div>
// hello-world.js ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );
12https://facebook.github.io/react/docs/hello-world.html
@drpicox
Hello World// This is translated by Babel to... ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );
// ...to this ReactDOM.render(React.createElement( 'h1', null, 'Hello, world!' ), document.getElementById('root'));
13https://babeljs.io/repl
@drpicox
Do not fear change• ReactJS only updates what changes
• Although everything is regenerated every time
• Open countdown example at codepen with chromehttp://codepen.io/drpicox/pen/yMKWGN
• Change View > Debug Mode (only works if registered)
• Inspect page
• You can see that only the span is updated(although this crude code changes everything)
14
JSX
https://facebook.github.io/react/docs/introducing-jsx.html
@drpicox
JSX// Just sugar syntax const element = <h1>Hello, world!</h1>;
// It is just one object const element = React.createElement( "h1", null, "Hello, world!" );
// one object that you can manipulate as object
16
@drpicox
JSX// May I? console.log(<h1>Hello!</h1>); console.log(<h1>Hello!</h1>.toString()); console.log([<h1>Hello!</h1>]); console.log({hello: <h1>Hello!</h1>}); console.log((() => <h1>Hello!</h1>)());
const salute = (what) => <h1>{what}!</h1>; console.log(salute('Hello'));
17
@drpicox
JSX// Have multiple elements (but one wrapper) const element = <h1><span>Hello</span>, world!</h1>;
// Generates const element = React.createElement( "h1", null, React.createElement( "span", null, "Hello" ), ", world!" );
18
@drpicox
JSX// It can be multiline (be careful using return) const element = <h1> Hello, world! </h1>;
19
@drpicox
JSX// It can be multiline (better) const element = <h1> Hello, world! </h1>;
20
@drpicox
JSX// It can be multiline (recommended) const element = ( <h1> Hello, world! </h1> );
21
@drpicox
JSX// Interpolate any JS expression const user = {name:'bob', lastName:'hoskings'}; const element = ( <h1> Hello, {user.name + ' ' + user.lastName}! </h1> );
// any expression, including JSX const element = <h1>Hello, {<span>world</span>}!
22
@drpicox
JSX// Add attributes (alias props) const element = <div tabIndex="0">...</div>;
// Computed attributes const element = <img src={user.imageUrl} />;
// class is className const element = <div className="body">...</div>;
23
@drpicox
JSX - Cross-site-scripting// It is just a string const title = response.veryMaliciousInput;
// This is save const element = <h1>{title}</h1>;
24
@drpicox
JSX// Is XML-like const element = ( <div> Hello <br/> World </div> );
// Elements must be terminated const element = ( <div> Hello <br> World </div> );
25
Rendering Elements
https://facebook.github.io/react/docs/rendering-elements.html
@drpicox
Rendering Elements// JSX elements are just memory objects const element = <h1>Hello, world</h1>;
27
@drpicox
Rendering Elements// Given an html DOM element <div id="root"></div>
// they can be rendered inside DOM const domElement = document.getElementById('root'); const reactElement = <h1>Hello, world</h1>;
ReactDOM.render( reactElement, /* into */ domElement );
28
@drpicox
Rendering Elements// As many times as you want // react only updates changes const domElement = document.getElementById('root');
let n = 0; setInterval(() => { ReactDOM.render(<h1>{n += 1}</h1>, domElement); }, 1000);
29
Components and Props
https://facebook.github.io/react/docs/components-and-props.html
@drpicox
Component definition// Functional component function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
// Class component class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
31
@drpicox
Render a Componentconst element = <Welcome name="Dave" />;
32
@drpicox
Composing Componentsfunction App() { return ( <div> <Welcome name="Alice" /> <Welcome name="Bob" /> <Welcome name="Dave" /> </div> ); }
33
@drpicox
Refactoring Componentsfunction Component(props) { return ( <div className="Comment"> <div className="UserInfo"> <img className="Avatar" src={props.author.avatarUrl} alt={props.author.name} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
34
@drpicox
Refactoring Componentsfunction Avatar(props) { return ( <img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} /> ); }
35
<img className="Avatar" src={props.author.avatarUrl} alt={props.author.name} />
@drpicox
Refactoring Componentsfunction Component(props) { return ( <div className="Comment"> <div className="UserInfo"> <Avatar user={props.author} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
36
@drpicox
Refactoring Componentsfunction Component(props) { return ( <div className="Comment"> <div className="UserInfo"> <Avatar user={props.author} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
37
@drpicox
Refactoring Componentsfunction UserInfo(props) { return ( <div className="UserInfo"> <Avatar user={props.user} /> <div className="UserInfo-name"> {props.user.name} </div> </div> ); }
38
@drpicox
Refactoring Componentsfunction Component(props) { return ( <div className="Comment"> <UserInfo user={props.author} /> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
39
@drpicox
Props are Read-Only// Render must be read-only function MissbehavingComponent(props) { return <h1>{props.n += 1}</h1>; }
40
State and Lifecycle
https://facebook.github.io/react/docs/state-and-lifecycle.html
@drpicox
Function to Class Componentfunction Clock(props) { return ( <div> <h1>Hello, world!</h1> <h2>It is {props.time}.</h2> </div> ); }
42
@drpicox
Function to Class Componentclass Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.time}.</h2> </div> ); } }
43
@drpicox
Function to Class Componentclass Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.time}.</h2> </div> ); } }
44
@drpicox
Setup Stateclass Clock extends React.Component { constructor(props) { super(props); this.state = {time: Date.now()}; } render() { ... } }
45
@drpicox
Setup Stateclass Clock extends React.Component { constructor(props) { ... } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.time}.</h2> </div> ); } }
46
@drpicox
Lifecycle Methodsclass Clock extends React.Component { constructor(props) { ... } componentDidMount() { /* TODO */ } componentWillUnmount() { /* TODO */ } render() { ... } }
47
@drpicox
Lifecycle Methodsclass Clock extends React.Component { constructor(props) { ... } componentDidMount() { this.timerId = setInterval( () => this.setState({time: Date.now()}), 1000 ); } componentWillUnmount() { /* TODO */ } render() { ... } }
48
@drpicox
Lifecycle Methodsclass Clock extends React.Component { constructor(props) { ... } componentDidMount() { ... } componentWillUnmount() { clearInterval(this.timerId); } render() { ... } }
49
@drpicox
setState Asynchronous// Wrong! setState({counter: this.state.counter + 1});
// Correct setState((prevState, props) => ({ counter: prevState.counter + 1 });
50
@drpicox
States are Mergedconstructor() { this.state = {posts: [], users: []}; }
componentDidMount() { getPosts((posts) => this.setState({posts})); getUsers((users) => this.setState({users})); }
51
@drpicox
State Isolatedfunction App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); }
52
Handling Events
@drpicox
onEvent={fn}function ButtonLogHello() { function handleClick() { console.log('The button is clicked'); }
return ( <button onClick={handleClick}> click me </button> ); }
54
@drpicox
onEvent={fn}// It received the event as parameter function ALogHello() { function handleClick(ev) { ev.preventDefault(); console.log('The link is clicked'); }
return ( <a href="#" onClick={handleClick}> click me </a> ); }
55
@drpicox
onEvent={fn}// It can inline functions (are expressions) function DivLogHello() { return ( <button onClick={() => console.log('oh')}> click me </button> ); }
56
@drpicox
Toggle Exampleclass Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true};
// This binding is necessary by `this` this.handleClick = this.handleClick.bind(this); } handleClick() { ... } ...
57
https://facebook.github.io/react/docs/handling-events.html
@drpicox
Toggle Exampleclass Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; } handleClick = () => { ... } ...
58
https://facebook.github.io/react/docs/handling-events.html
Conditional Rendering
https://facebook.github.io/react/docs/conditional-rendering.html
@drpicox
Conditional Renderingfunction UserGreeting(props) { return <h1>Welcome back!</h1>; }
function GuestGreeting(props) { return <h1>Please sign up.</h1>; }
60
@drpicox
By returnfunction Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <UserGreeting />; } return <GuestGreeting />; }
ReactDOM.render( // Try changing to isLoggedIn={true}: <Greeting isLoggedIn={false} />, document.getElementById('root') );
61
@drpicox
By variablerender() { const isLoggedIn = this.state.isLoggedIn;
let button = null; if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; }
return ( <div> <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); }
62
@drpicox
By && expressionfunction Mailbox(props) { const unreadMessages = props.unreadMessages;
return ( <div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div> ); }
63
@drpicox
By ?: expressionrender() { const isLoggedIn = this.state.isLoggedIn; return ( <div> {isLoggedIn ? ( <LogoutButton onClick={this.handleLogoutClick} /> ) : ( <LoginButton onClick={this.handleLoginClick} /> )} </div> ); }
64
@drpicox
Prevent Renderfunction WarningBanner(props) { if (!props.warn) { return null; }
return ( <div className="warning"> Warning! </div> ); }
65
Lists and Keys
@drpicox
Remember...const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map((n) => n * 2); console.log(doubled);
// [2, 4, 6, 8, 10]
67
@drpicox
Render Multiple// Render a reactElements array const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li>{number}</li> );
ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );
68
@drpicox
Render Multiple// Refactor into component function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li>{number}</li> ); return ( <ul>{listItems}</ul> ); }
const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') );
69
@drpicox
Keyfunction NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ); return ( <ul>{listItems}</ul> ); }
70
@drpicox
Key - indexconst todoItems = todos.map((todo, index) => // Only do this if items have no stable IDs <li key={index}> {todo.text} </li> );
71
@drpicox
Key - Refactor Componentfunction NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ); return ( <ul>{listItems}</ul> ); }
72
@drpicox
Key - Refactor Componentfunction ListItem(props) { return ( // Wrong! should not be here <li key="props.number.toString()"> {props.number} </li> ); }
73
@drpicox
Key - Refactor Componentfunction NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // Correct: key must remain with map <ListItem key={number.toString()} number={number} /> {number} ); return ( <ul>{listItems}</ul> ); }
74
@drpicox
Key - Uniqueness// Cannot repeat keys in the same list, but... function DoubleList(props) { return ( <div> <NumberList numbers={props.numbers} /> <NumberList numbers={props.numbers} /> <NumberList numbers={props.numbers} /> </div> ); }
75
@drpicox
.map inside JSXfunction NumberList(props) { const numbers = props.numbers; return ( <ul> {numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul> ); }
76
Forms
@drpicox
Controlled Componentsclass NameForm extends React.Component { constructor() { ... } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { ... } render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> </form> ); } }
78
@drpicox
Controlled Componentsclass NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; } handleChange = (event) => { ... } handleSubmit = (event) => { ... } render() { ... } }
79
@drpicox
Controlled Componentsclass NameForm extends React.Component { constructor() { ... } handleChange = (event) => { this.setState({ value: event.target.value.toLowerCase() }); } handleSubmit = (event) => { ... } render() { ... } }
80
@drpicox
Textarea<textarea value={this.state.value} onChange={this.handleChange} />
81
@drpicox
Select<select value={this.state.value} onChange={this.handleChange}> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option value="coconut">Coconut</option> <option value="mango">Mango</option> </select>
82
@drpicox
Multiple Inputs<input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} />
<input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} />
83
@drpicox
Multiple InputshandleInputChange(event) { const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name;
this.setState({ [name]: value }); }
84
Synchronized States
https://facebook.github.io/react/docs/lifting-state-up.html
@drpicox
Synchronizing - Consumer// We want an <input> to generate new values for function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; }
86
@drpicox
Synchronizing - Coordinatorclass Calculator extends React.Component { constructor(props) { ... } handleChange = (e) => this.setState({temperature: e.target.value}); render() { const temperature = this.state.temperature; return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset> ); } }
87
@drpicox
Synchronizing - Producerclass Calculator extends React.Component { constructor(props) { ... } handleChange = (e) => this.setState({temperature: e.target.value}); render() { const temperature = this.state.temperature; return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset> ); } }
88
@drpicox
Synchronizing - Producerclass TemperatureInput extends React.Component { constructor(props) { ... } handleChange = (e) => this.setState({temperature: e.target.value}); render() { const temperature = this.state.temperature; const scaleName = scaleNames[this.props.scale]; return ( <fieldset> <legend>Enter temperature in {scaleName}:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }
89
@drpicox
Synchronizing - Producerclass Calculator extends React.Component { constructor(props) { ... } handleChange = (e) => this.setState({temperature: ...?}); render() { const temperature = this.state.temperature; return ( <div> <TemperatureInput scale="c" ...? ...? /> <TemperatureInput scale="f" ...? ...? /> <BoilingVerdict celsius={parseFloat(temperature)} /> </div> ); } }
90
@drpicox
Synchronizing - Producerclass TemperatureInput extends React.Component { constructor(props) { ... } handleChange = (e) => this.props.onTemperatureChange(e.target.value); render() { const temperature = this.props.temperature; const scaleName = scaleNames[this.props.scale]; return ( <fieldset> <legend>Enter temperature in {scaleName}:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }
91
@drpicox
Synchronizing - Coordinatorclass Calculator extends React.Component { constructor(props) { ... } handleChange = (e) => this.setState({temperature: ...?}); render() { const temperature = this.state.temperature; return ( <div> <TemperatureInput scale="c" ...? ...? /> <TemperatureInput scale="f" ...? ...? /> <BoilingVerdict celsius={parseFloat(temperature)} /> </div> ); } }
92
@drpicox
Coordinator - Inputsclass Calculator extends React.Component { ... render() { const scale = this.state.scale; const temperature = this.state.temperature; const celsius = convertIf(scale === 'f', temperature, toCelsius); const fahrenheit = convertIf(scale === 'c', temperature, toFahrenheit); return ( <div> <TemperatureInput scale="c" temperature={celsius} ...? /> <TemperatureInput scale="f" temperature={fahrenheit} ...? /> <BoilingVerdict celsius={parseFloat(celsius)} /> </div> ); } }
93
@drpicox
Coordinator Outputsclass Calculator extends React.Component { ... handleCelsiusChange = (temperature) => this.setState({scale: 'c', temperature}); handleFahrenheitChange = (temperature) => this.setState({scale: 'f', temperature}); render() { const scale = this.state.scale; const temperature = this.state.temperature; const celsius = convertIf(scale === 'f', temperature, toCelsius); ... <TemperatureInput scale="c" temperature={celsius} onTemperatureChange={this.handleCelsiusChange} /> <TemperatureInput scale="f" temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange} /> ... } }
94
@drpicox
Coordinator Outputsfunction convertIf(test, temperature, convert) { if (test) { return tryConvert(temperature, convert); } return temperature; }
95
@drpicox
Exercise
96
https://codepen.io/drpicox/pen/ryrypJ
Exercise React Temperature
Component Composition
https://facebook.github.io/react/docs/composition-vs-inheritance.html
@drpicox
Java 1.0
98
http://web.mit.edu/java_v1.0.2/www/apibook/javag2.htm
An application should override the action method (II-§1.10.1) of the button or of one of its containing windows in order to cause some action to occur.
Button+ action(...)
MyButton+ action(...)
@drpicox
Java 1.1
99
In 1.1, we aimed at solving some major AWT (Abstract Window Toolkit) deficiencies, with a strong focus on quality and performance. The AWT
enhancements include [...], a delegation-based event model, [...].
Button+ actionActionListener(...)
https://www.cs.princeton.edu/courses/archive/fall97/cs461/jdkdocs/guide/awt/index.html
<i> ActionListener+ actionPerformed(...) = 0
MyActionListener+ actionPerformed(...)
*
@drpicox
Java 1.2
100
When an Action object is added to such a container, the container: Creates a component that is appropriate for that container (a toolbar creates a button component, for example), Gets
the appropriate property(s) from the Action object to customize the component (for example, the icon image and flyover text)....
<i> Action+ actionPerformed(...) = 0 + isEnabled(): bool + setEnabled(b) + getValue(key): Object + putValue(key, value) + addPropertyChangeListener(...) + removePropertyChangeListener(...)
http://www.kbs.twi.tudelft.nl/Documentation/Programming/Java/jdk1.2/api/javax/swing/Action.html
@drpicox
Children Composition
101
CardContents
@drpicox
Children Composition<card> <h1>Welcome</h1> <p> Find here a complete list of all the things that you love. </p> </card>
102
@drpicox
Children Compositionfunction Card(props) { return ( <div className="card"> {props.children} </div> ); }
103
@drpicox
Many Children Composition
104
SplitPaneLeft Right
@drpicox
Many Children Composition<div class="SplitPane"> <div class="SplitPane-left"> <Contacts /> </div> <div class="SplitPane-left"> <Chat /> </div> </div>
105
@drpicox
Many Children Compositionfunction SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); }
106
@drpicox
Many Children Compositionfunction App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
107
@drpicox
Specializationfunction Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> </FancyBorder> ); }
108
@drpicox
Specializationfunction WelcomeDialog(props) { return ( <Dialog title="Welcome" message="Thanks for visiting!" /> ); }
109
High Order Components (I)
https://facebook.github.io/react/docs/higher-order-components.html
@drpicox
Like high order functionsconst safeConvert = (convert) => { return (temperature) => { const input = parseFloat(temperature); if (Number.isNaN(input)) { return ''; } const output = convert(input); const rounded = Math.round(output * 100) / 100; return rounded.toString(); } }
111
@drpicox
Like high order functionsconst safeConvert = (convert) => { return (temperature) => { ... } }
const safeKelvinToCelsius = safeConvert(kelvinToCelsius);
const celsius = safeKelvinToCelsius(kelvin);
112
@drpicox
Specific Componentclass TemperatureInputCelsius extends React.Component { handleChange = (e) => this.props.onTemperatureChange( safeCelsiusToKelvin(e.target.value); ); render() { const temperature = this.props.temperature; const celsius = safeKelvinToCelsius(temperature); return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } } 113
@drpicox
Specific Componentclass TemperatureInputFahrenheit extends React.Component { handleChange = (e) => this.props.onTemperatureChange( safeFahrenheitToKelvin(e.target.value); ); render() { const temperature = this.props.temperature; const celsius = safeKelvinToFahrenheit(temperature); return ( <fieldset> <legend>Enter temperature in Fahrenheit:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } } 114
@drpicox
Generic Componentclass TemperatureInput? extends React.Component { handleChange = (e) => this.props.onTemperatureChange( toKelvin(e.target.value); ); render() { const temperature = this.props.temperature; const local = toLocal(temperature); return ( <fieldset> <legend>Enter temperature in {scaleName}:</legend> <input value={local} onChange={this.handleChange} /> </fieldset> ); } } 115
@drpicox
Generic Componentfunction makeTemperatureInput(toKelvin, toLocal, scaleName) { return class extends React.Component { handleChange = (e) => this.props.onTemperatureChange( toKelvin(e.target.value); ); render() { const temperature = this.props.temperature; const local = toLocal(temperature); return ( <fieldset> <legend>Enter temperature in {scaleName}:</legend> <input value={local} onChange={this.handleChange} /> </fieldset> ); } } }
116
@drpicox
Using Generic Componentconst TemperatureInputCelsius = makeTemperatureInput( safeCelsiusToKelvin, safeKelvinToCelsius, 'Celsius' );
const TemperatureInputFahrenheit = makeTemperatureInput( safeFahrenheitToKelvin, safeKelvinToFahrenheit, 'Fahrenheit' );
const TemperatureInputKelvin = makeTemperatureInput( identity, identity, 'Kelvin' ); 117
@drpicox
Using Generic Componentconst TemperatureInputCelsius = makeTemperatureInput( safeConvert(celsiusToKelvin), safeConvert(kelvinToCelsius), 'Celsius' );
const TemperatureInputFahrenheit = makeTemperatureInput( safeConvert(fahrenheitToKelvin), safeConvert(kelvinToFahrenheit), 'Fahrenheit' );
const TemperatureInputKelvin = makeTemperatureInput( (x) => x, (x) => x, 'Kelvin' ); 118
@drpicox
High Order Componentclass Thermostat extends React.Component { constructor(props) { ... } handleChangeMax = (e) => ... handleChangeMin = (e) => ... render() { return ( <div> Max <TemperatureInput temperature={this.state.max} onTemperatureChange={this.handleChangeMax} /> Min <TemperatureInput temperature={this.state.min} onTemperatureChange={this.handleChangeMin} /> </div> ); } }
119
@drpicox
High Order Componentfunction makeThermostat(TemperatureInput) { return class extends React.Component { constructor(props) { ... } handleChangeMax = (e) => ... handleChangeMin = (e) => ... render() { return ( <div> Max <TemperatureInput temperature={this.state.max} onTemperatureChange={this.handleChangeMax} /> Min <TemperatureInput temperature={this.state.max} onTemperatureChange={this.handleChangeMax} /> </div> ); } } } 120
@drpicox
High Order Componentfunction makeThermostat(TemperatureInput) { return class extends React.Component { ... } }
const ThermostatCelsius = makeThermostat(TemperatureInputCelsius);
const ThermostatFahrenheit = makeThermostat(TemperatureInputFahrenheit);
const ThermostatKelvin = makeThermostat(TemperatureInputKelvin); 121
@drpicox
Debug: displayNamefunction makeTemperatureInpu( toKelvin, toFahrenheit, scaleName ) { class TemperatureInput extends React.Component { ... } TemperatureInput.displayName = `TemperatureInput(${scaleName})`; return TemperatureInput; }
122
@drpicox
Debug: displayNamefunction makeThermostat(TemperatureInput) { class Thermostat extends React.Component { ... } Thermostat.displayName = `Thermostat(${getDisplayName(TemperatureInput)})`; return Thermostat; }
123
@drpicox
Warning// DON'T use HOCs in render functions! function App() { const TemperatureInputAbc = makeTemperatureInput(a, b, c); return <TemperatureInputAbc />; } class App extends React.Component { render() { const TemperatureInputAbc = makeTemperatureInput(a, b, c); return <TemperatureInputAbc />; } }
124
@drpicox
More in docs• Use HOCs For Cross-Cutting Concerns
• Don't Mutate the Original Component. Use Composition.
• Convention: Pass Unrelated Props Through to the Wrapped Component
• Convention: Maximizing Composability
• Convention: Wrap the Display Name for Easy Debugging
• Caveats
125
Containers
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
@drpicox
Convention• Types of components
• Routers
• Containers
• Presentational
127
@drpicox
Routes• Routers
• Decides which component to render
• Create components dynamically
• Usually provided by library
128
@drpicox
Containers• Knows how to load or mutate data
• Observe Stores
• Dispatch Actions
• Other system interactions
• Always stateful (class notation)
• Renders nothing
• Delegates render to a Presentational Component
• Configures its props
129
@drpicox
Presentational• Knows how to render things
• Data and callbacks only via props
• does not interact with the application
• Usually functional (not need state)
• Also called Components
130
@drpicox
class Clock extends React.Component { constructor(props) { ... } componentDidMount() { this.timerId = setInterval( () => this.setState({time: Date.now()}), 1000 ); } componentWillUnmount() { clearInterval(this.timerId); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.time}.</h2> </div> ); } }
131
Presentational
Container
@drpicox
class Clock extends React.Component { constructor(props) { ... } componentDidMount() { this.timerId = setInterval( () => this.setState({time: Date.now()}), 1000 ); } componentWillUnmount() { clearInterval(this.timerId); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.time}.</h2> </div> ); } }
132
Presentational
Container
@drpicox
Presentational Refactorfunction Clock { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.time}.</h2> </div> ); }
133
@drpicox
Container Refactorclass IntervalClock extends React.Component { constructor(props) { ... } componentDidMount() { this.timerId = setInterval( () => this.setState({time: Date.now()}), 1000 ); } componentWillUnmount() { clearInterval(this.timerId); } render() { return <Clock time={this.state.time} />; } }
134