Notes from My Journey Learning Javascript and React.js
By David Xiao
TLDR
As a recovering C++ developer learning React, I put together some notes along the journey.
Some of the notes and example code were extracted from the materials listed in the reference. I’ve tried to include links but feel free to let me know if I missed something.
Credit goes to the original authors.
Reference
- Step-by-step guide. Great learning material.
What is React.js
React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It has a few building blocks such as elements, components etc.
Elements
An element describes what you want to see on the screen:
const element = <h1>Hello world</h1>;
const div1 = <div />;
Components
Elements such as <div />
are defined in HTML5. React extends it by introducing user-defined elements such as
<Welcome name="Sara" />
Welcome
is a user-defined component. In React, always start component names with a capital letter to follow the naming convention.
Why Components
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
React component can be declared as a JS function or a JS class. Either way, it accepts input (called “props”) and return React elements describing what should appear on the screen.
When React sees an element representing a user-defined component, it wraps up JSX attributes and children and passes it as a single object (called props
) to the component implicitly without user code.
props
stands for properties.
React function
An example:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
JS code starts at the top of the file. It gets executed from top to bottom. There is no entry point such as what “main()” has to do in C++.
What happens in the example above is:
-
React calls ReactDOM.render() with the
<Welcome name="Sara" />
-
React sees
Welcome
is a user-defined component and calls its corresponding function with{name: 'Sara'}
as the props. -
Welcome component returns
<h1>Hello, Sara</h1>
as the result. -
React takes care of updating the DOM with
<h1>Hello, Sara</h1>
.
React class
The function component example above can be equivalently rewritten as a class component:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
React class component always “inherited” from React.Component
.
render()
is the only method that any class “inherited” from React.Component
must declare.
Think of render()
as a C++ pure virtual function in React.Component
.
A derived class would have to implement it before the class can be instantiated.
Calling a component in another component
Let’s take function component as an example.
graph TD;
Comment-->UserInfo-->Avatar;
Comment passed the value of its own props.author
to UserInfo.
function Comment(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>
);
}
UserInfo’s props contains a key value pair “user=…” received from Comment. The value then gets passed down to Avatar.
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
Props
Props gets passed to the constructor of class and stored as a class variable. Any method thereafer can reference it using this.props
In a class component, think of this.props
as a const reference member variable in C++.
It is initiated within constructor(props)
where super(props)
is called, the superclass always being React.Component
.
We recommend naming props from the component’s own point of view rather than the context in which it is being used. The rationale being: It doesn’t need to know context such as where it is being rendered.
That’s a sort of isolation that helps in producing clear code.
Props are Immutable
Whether you declare a component as a function or a class, it must never modify its own props.
The following code is valid in javascript syntax but should not be used as React component as it changes its own input:
function withdraw(account, amount) {
account.total -= amount;
}
Think of JS function parameter as pass by reference in C++.
In React, treat props
as if it is const reference in C++.
Class, State and Lifecycle
One difference between class and function is that class has a special object called this.states
. states acts as a “container” that preserves variables between calls. In other words, class oject is “stateful” while function is “stateless”.
A class has a few built-in methods including:
-
constructor()
. See here -
componentDidMount()
. It is called when the object is rendered to the DOM for the first time. See here -
componentWillUnmount()
. It is called when the DOM produced by the object is removed. See here
A diagram to help understand lifecycle:
For a visual reference, click here
state
is a built-in object in class.
Click to see code example and explainations
class Clock extends React.Component {
constructor(props) {
// for a React.Component subclass, you should call
// super(props) before any other statement
super(props);
// in constructor, you should not call setState(),
// instead, assign the initial state to this.state directly
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
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></h2></h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
-
When
<Clock />
is passed toReactDOM.render()
, React calls the constructor of the Clock component. Since Clock needs to display the current time, it initializesthis.state
with an object including the current time. We will later update this state. -
React then calls the Clock component’s
render()
method. This is how React learns what should be displayed on the screen. React then updates the DOM to match the Clock’s render output. -
When the Clock output is inserted in the DOM, React calls the
componentDidMount()
lifecycle method. Inside it, the Clock component asks the browser to set up a timer to call the component’stick()
method once a second. -
Every second the browser calls the
tick()
method. Inside it, the Clock component schedules a UI update by callingsetState()
with an object containing the current time.👉 Important: Thanks to the
setState()
call, React knows the state has changed, and calls the render() method 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 method so the timer is stopped.
Props vs State
This is a growing list.
“rendered” value
In React, both this.props and this.state represent the rendered values, i.e. what’s currently on the screen.
Both props and state can be accessed by “this”
Both this.state
and this.props
are valid within the class scope.
Props are immutable
It is not supposed to be modified in any way. If the component needs to be “stateful” during the calls, always use state.
Avoid Copying Props into State
constructor(props) {
super(props);
// Don't do this!
this.state = { color: props.color };
}
This is a common mistake.
It’s unnecessary (use this.props.color
instead) and prone to bugs (updates to the color
prop won’t be reflected in the state).
Only use it if you want to disregard prop updates. In that case, it makes sense to rename the prop to be called initialColor
or defaultColor
. You can then force a component to “reset” its internal state by changing its key when necessary.
Read this post on avoiding derived state to learn about what to do if you think you need some state to depend on the props.
State Updates May Be Asynchronous
setState()
“schedules” an update to a component’s state object. When state changes, the component responds by re-rendering.
Consider using setState() that accepts a function rather than an object when you need to update state variables.
React may batch multiple setState() calls into a single update for performance.
In React, state
must only be updated by setState()
(only exception is within constructor()
).
This is because manual updates won’t trigger UI updates since React doesn’t know state has changed.
To fix it, use a second form of setState() that accepts a “updater” function object as parameter.
// Wrong. counter may not be updated immediately since setState() may be delayed in execution by React
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct. See explaination above
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
In React, “updater” function takes the following signature:
(state, props) => stateChange
The return value of the updater
is shallowly merged with state. For example, in the following code, the return value of the function. will be assigned to state.counter
:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
State holds all its variables
React merges the object you provide into the current state.
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
When you update comments
with the following code, it leaves this.state.posts
intact, but completely replaces this.state.comments
.
State is not accessible to other components
State is not accessible to any component other than the one that owns and sets it.
It is the philosophy that neither parent nor child components can know if a certain component is stateful or stateless, and they shouldn’t care its implementation details, such as whether it is defined as a function or a class.
In React apps, whether a component is stateful or stateless is considered an implementation detail of the component that may change over time. You can use stateless components inside stateful components, and vice versa.
Be cautious about using “this” in a class method
Unlike in C++ where this
is accessible in any class method including in the constructor, in JS, this
is undefined
until it is bound.
First, let’s take a look at this example where I purposely made an attempt to access this
in constructor and get the following error:
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
at pen.js:-12
Second, in member methods this
is undefined
until it is bound.
In the following example code, if you forget to bind this.handleClick
and pass it to onClick
, this will be undefined when the function is actually called.
This is not React-specific; it is a part of how functions work in JavaScript.
Generally, if you refer to a method without () after it, such as onClick={this.handleClick}, you should bind that method.
There are two (recommended) ways to do it:
// bind a method in constructor
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);
}
// or the following way
handleClick = () => {
console.log('this is:', this);
}
In either way, the method is bound and the render() will work:
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}