ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
- Declarative
- Component-Based
- Learn Once, Write Anywhere
class HelloMessage extends React.Component {
render() {
return (
<div>
Hello {this.props.name}
</div>
);
}
}
ReactDOM.render(
<HelloMessage name="John" />,
mountNode
);
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
}
tick() {
this.setState((prevState) => ({
seconds: prevState.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div>
Seconds: {this.state.seconds}
</div>
);
}
}
ReactDOM.render(<Timer />, mountNode);
const element = <h1>Hello, world!</h1>;
This is called JSX, a syntax extension to JavaScript. JSX produces React “elements”
You can embed any JavaScript expression in JSX by wrapping it in curly braces.
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
After compilation, JSX expressions become regular JavaScript objects.
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
You may use quotes to specify string literals as attributes. You may also use curly braces to embed a JavaScript expression in an attribute
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
Don’t put quotes around curly braces when embedding a JavaScript expression in an attribute. You should either use quotes (for string values) or curly braces (for expressions), but not both in the same attribute.
Since JSX is closer to JavaScript than HTML, React DOM uses camelCase property naming convention instead of HTML attribute names.
If a tag is empty, you may close it immediately with />
, like XML
const element = <img src={user.avatarUrl} />;
JSX also may contained children
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
By default, React DOM escapes any values embedded in JSX before rendering them. Thus it ensures that you can never inject anything that’s not explicitly written in your application. Everything is converted to a string before being rendered. This helps prevent XSS (cross-site-scripting) attacks.
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()
performs a few checks to help you write bug-free
const element = <h1>Hello, world</h1>;
Unlike browser DOM elements, React elements are plain objects, and are cheap to create. React DOM takes care of updating the DOM to match the React elements.
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
React elements are immutable.
React DOM compares the element and its children to the previous one, and only applies the DOM updates necessary to bring the DOM to the desired state
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
Functional:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Class:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Sara" />;
When React sees an element representing a user-defined component, it passes JSX attributes to this component as a single object. We call this object “props”.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
Components can refer to other components in their output. This lets us use the same component abstraction for any level of detail
Split components into smaller components.
Whether you declare a component as a function or a class, it must never modify its own props.
function sum(a, b) {
return a + b;
}
All React components must act like pure functions with respect to their props.
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
import { Component } from 'react';
class Clock extends Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
We can't/mustn't change the props
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Add class constructor
. A constructor
is a special method for creating and initializing an object created with a class
.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
A react class should always call the base constructor with props.
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
We want to set up a timer whenever the Clock is rendered to the DOM for the first time. This is called “mounting” in React. We also need make sure that we clear the timer when the DOM element of the Clock was removed, or called "unmounting".
React have various lifecycle hooks, but for this case we going to use 2 hooks.
componentDidMount
: runs after the component has been mounted, or rendered to the DOM. Its the best place to run any 3rd party plugin initializing that requiring for DOM to be ready
componentWillUnmount
: runs when a component was unmounted, or when the DOM rendered are removed, for example when navigating from one page to another
Best place to run the timer is in the componentDidMount
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
And best place to clear the timer is in componentWillUnmount
componentWillUnmount() {
clearInterval(this.timerID);
}
We can then use setState to mutate the clock value whenever the timer was called
tick() {
this.setState({
date: new Date()
});
}
Do Not Modify State Directly
// Wrong
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
State maybe async
Multiple setState() call maybe call in batch for Performance
// Wrong (maybe)
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
Update Are Merged
React (shallow) merges the object you provide into the current state.
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
State should be encapsulated or local to the component that own and sets it
<FormattedDate date={this.state.date} />
Within the component, it can access this state from the parents through their props
, and it wouldn't know where does it came from or whether it came as a props
or a state
of the parents. They shouldn't care about it.
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
A stateless component should have one job and do that job well. In most cases is to render it regardless where it came from.
This flow known commonly as unidirectional or top-down data flow. You can imagine a component tree like a waterfall. This allow all the component to be truly isolated and each of them will run independently
Reacts event are name in CamelCase.
<button onClick={launchNuclear}>
Trump Button
</button>
You can't use return false
, the traditional way to prevent default browser behavior. It must be done explicitly using the preventDefault
method.
// HTML way
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
// react way
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
event
returned from the handler is a synthetic event, which is a cross-browser wrapper around the browser’s native event
You don't need to call addEventListener
in React, instead, you just assign the event handler.
When defining component as a react class
, it is common to create the event handler as a method within the class.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
function 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')
);
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
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>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
true && expression
= expression
false && expression
= false
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
Can also be use to render component conditionally
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true}
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}
render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
);
Returning null
within the render()
method will not affect the lifecycle method -- it will still be triggered.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity
Keys only make sense in the context of the surrounding array
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify the key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Wrong! The key should have been specified here:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Keys used within arrays should be unique among their siblings.
However they don’t need to be globally unique
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
It’s convenient to have a JavaScript function that handles the submission of the form and has access to the data that the user entered into the form
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite La Croix flavor:
<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>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
For multiple selection, use the multiple
attribute and pass an array of value
<select multiple={true} value={['B', 'C']}>
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
Sometimes, you may need to have several components to reflect the same changing data. This can be done by lifting the shared state up to their closest common ancestor.
function BoilingVerdict(props) {
if (props.celcius >= 100) {
return <p>The water would boil</p>
}
return <p>The water would not boil</p>
}
class Calculator extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.state = {
temperature: ''
}
}
handleChange(e) {
this.setState({
temperature: e.target.value
})
}
render() {
const temp = this.state.temperature
return (
<fieldset>
<legend>Enter temperature in celcius:</legend>
<input
value={temp}
onChange={this.handleChange} />
<BoilingVerdict
celcius={parseFloat(temp)} />
</fieldset>
)
}
}
Assume that we also want to provide a Fahrenheit input and also making sure that both of the input sync
const scaleNames = {
c: 'Celcius',
f: 'Fahrenheit'
}
class TemperatureInput extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.state = {
temperature: ''
}
}
handleChange(e) {
this.setState({
temperature: e.target.value
})
}
render() {
const temp = this.state.temperature
const scale = this.props.scale
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input
value={temp}
onChange={this.handleChange} />
</fieldset>
)
}
}
class Calculator extends Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
)
}
}
BoilingVerdict
can't display the verdict because the Calculator
component doesn't have the information about the temperature, now that it live inside TemperatureInput
component.
function toCelcius(f) {
return (f - 32) * 5 / 9
}
function toFahrenheit(c) {
return (c * 9 / 5) + 32
}
function conversion(temp, convert) {
const input = parseFloat(temp)
if (Number.isNaN(input)) {
return ''
}
const output = convert(input)
const rounded = Math.round(output * 1000) / 1000
return rounded.toString()
}
We can accomplished this by moving the state to their common ancestor, which is known as lifting state up
class TemperatureInput extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value)
}
render() {
const temp = this.props.temperature
const scale = this.props.scale
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input
value={temp}
onChange={this.handleChange} />
</fieldset>
)
}
}
The state
should be moved to the Calculator
component so it will be a single source of truth
class Calculator extends Component {
constructor(props) {
super(props)
this.handleCelciusChange = this.handleCelciusChange.bind(this)
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this)
this.state = {
temperature: '',
scale: 'c'
}
}
handleCelciusChange(e) {
this.setState({
scale: 'c'
temperature: e.target.value
})
}
handleFahrenheitChange(e) {
this.setState({
scale: 'f'
temperature: e.target.value
})
}
render() {
const { scale, temperature } = this.state
const celcius = scale === 'f' ? conversion(temperature, toCelcius) : temperature
const fahrenheit = scale === 'c' ? conversion(temperature, toFahrenheit) : temperature
return (
<div>
<TemperatureInput
scale="c"
temperature={celcius}
onTemperatureChange={this.handleCelciusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celcius={parseFloat(celcius)} />
</div>
)
}
}
There should be a single "source of truth" for any data changes in a react application
Remember: top-down data flow