Aims: This article is to introduce some basic concepts by using the "Cliking Clock" case.
1.Elements & DOM node
DOM node (.html file): everything inside it will be managed by React DOM.
Elements (.js file) are the smallest building blocks of React apps. To render a React element into a root DOM node, pass both to ReactDOM.render()
. Elements are immutable (Once you create an element, you can't change its children or attributes). An element is like a single frame in a movie: it represents the UI at a certain point in time.
One way to update the UI is to create a new element, and pass it to ReactDOM.render()
:
// index.js (Babel)
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);
// index.html (DOM node)
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
2.Components & Props
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.Conceptually, components are like JS functions. They accept arbitrary inputs (called "props") and return React elements describing what should appear on the screen.
Previously, we showed how React elements represent DOM tags. However, elements can also represent user-defined components:
// index.js
// ******functional way to define a component******
function Welcome(props) {
return <h1>Hello, {props.name}</h1>; //return an element
}
// ******use an ES6 class to define a component******
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>; //return an element
}
}
//elements represent user-defined components
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
- Recap:
- We call
ReactDOM.render()
with the<Welcome name="Sara" />
element. - React calls the
Welcome
component with{name: 'Sara'}
as the props. - The
Welcome
component returns a<h1>Hello, Sara</h1>
element as the result. - React DOM efficiently updates the DOM to match
<h1>Hello, Sara</h1>
.
- NOTE:
- Always start component names with a capital letter.
Eg:<div />
represents a DOM tag, but<Welcome />
represents a component and requires Welcome to be in scope. - Props are Read-Only. Whether you declare a component as a function or a class, it must never modify its own props. All React components must act like pure functions (do not attempt to change the inputs, and always return the same result for the same inputs) with respect to their props.
-
Components can refer to other components in their output. This lets us use the same component abstraction for any level of detail. A button, a form, a dialog, a screen: in React apps, all those are commonly expressed as components.
Eg: we can create anApp
component that rendersWelcome
many times:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
3.State & Lifecycle
In this section, we will learn how to make the Clock
component in the "Cliking Clock" case truly reusable and encapsulated. It will set up its own timer and update itself every second. By achieving this, we need to add state
(see below) to the Clock
component.
State is similar to props, but it is private and fully controlled by the component. Local state is exactly a special feature available only to the class-defined component other than the functional component.
Here are the steps showing how to convert a functional component to a class-defined component with state
used:
- Converting a Function to a Class
- Create an ES6 class with the same name that
extends React.Component
. - Add a single empty method to it called
render()
. - Move the body of the function into the
render()
method. - Replace
props
withthis.props
in therender()
body. - Delete the remaining empty function declaration.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- Adding Local State to a Class
- Replace
this.props.date
withthis.state.date
in therender()
method. - Add a class constructor that assigns the initial
this.state
.
Note: how to passprops
to the base constructor. - Remove the
date
prop from the<Clock />
element:
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>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
- Adding Lifecycle Methods to a class
-
Set up a timer using the special method
componentDidMount()
whenever theClock
is rendered to the DOM for the first time, which is called "mounting" in React. -
Clear the timer using another special method
componentWillUnmount()
whenever the DOM produced by theClock
is removed, which is called "unmounting" in React.
NOTE: These 2 methods are called "lifecycle hooks". - Implement the
tick()
method that will be called every second. It will usethis.setState()
to schedule updates to the component local state:
// index.js
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
// mounting, set up a timer which is to call the tick() method by every 1000ms
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),1000
);
}
// unmounting, clear the timer
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);}
}
// we write the Clock once and it can updates the UI every second by itself.
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
-
Recap:
- When
<Clock />
is passed toReactDOM.render()
, React calls theconstructor
of theClock
component. SinceClock
needs to display the current time, it initializesthis.state
with an object including the current time, which state will be updated later.
- When
- React then calls the
Clock
'srender()
method to know what should be displayed on the screen. And then updates the DOM to match theClock
's render output. - Then React calls the
componentDidMount()
lifecycle hook. Inside it, theClock
asks the browser to set up a timer to calltick()
once a second. - Every second the browser calls the
tick()
method. Inside it, theClock
schedules a UI update by callingsetState()
with an object containing the current time. Thanks to thesetState()
call, React knows the state has changed, and callsrender()
again to learn what should be on the screen. This time,this.state.date
in therender()
method will be different, and so the render output will include the updated time. React updates the DOM accordingly. - If the Clock component is ever removed from the DOM, React calls the
componentWillUnmount()
lifecycle hook so the timer is stopped.
-
NOTE About Using State Correctly
-
Do Not Modify State Directly, the only place where you can assign
this.state
is theconstructor()
. Eg:
-
Do Not Modify State Directly, the only place where you can assign
// Wrong
this.state.comment = 'Hello';
Instead, use setState()
:
// Correct
this.setState({comment: 'Hello'});
-
Updating the State May Be Asynchronous. Because
this.props
andthis.state
may be updated asynchronously, you cannot rely on their values for calculating the next state. Eg:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
Instead, use a second form of setState()
that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
// Correct Using Arrow Function
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
//Also Correct Using Regular Function
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
- State Updates are Merged. Eg, your state may contain several independent variables:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
Then you can update them independently with separate setState()
calls:
componentDidMount() {
fetchPosts().then(response => {
this.setState({posts: response.posts});
});
fetchComments().then(response => {
this.setState({comments: response.comments});
});
}
The merging is shallow, so this.setState({comments})
leaves this.state.posts
intact (unchanged), but completely replaces this.state.comments
.
4.Handling Events
Handling events with React elements is very similar to handling events on DOM elements. There are some syntactic differences:
- React events are named using camelCase, rather than lowercase.
- With JSX you pass a function as the event handler, rather than a string.
-
Cannot return
false
to prevent default behavior in React. You must callpreventDefault
explicitly. Eg:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}> Click me </a>
);
}
However, in HTML, use false
to prevent the default link behavior of opening a new page:
<a href="#" onclick="console.log('The link was clicked.'); return false"> Click me </a>
- In React you no need to call
addEventListener
to add listeners to a DOM element after it is created. Instead, just provide a listener when the element is initially rendered.
When you define a component using an ES6 class, a common pattern is for an event handler to be a method on the class.
Eg, this Toggle component renders a button that lets the user toggle between "ON" and "OFF" states:
class ClickTest extends React.Component{
constructor(props){
super(props);
this.state = {test : true};
// This binding is necessary to make `this` works in the callback
this.makeClick = this.makeClick.bind(this);
}
makeClick(){
this.setState(preTest => ({test: !preTest.test}));
}
render(){
return (<button onClick = {this.makeClick}>{this.state.test ? "on" : "off"}</button>);
}
}
ReactDOM.render(
<ClickTest />,
document.getElementById('root')
);