When the Coronavirus lockdown was about to start, I realized that this would be a wonderful time to pick up new skills which I have been putting off. So, I made a list of things I wanted to do and went to accomplishing them one by one. This post is about learning ReactJS and the notes I made on the way.
React Notes
- React is a JavaScript library (not framework)
JSX Syntax
const heading = <h1 className = "site-heading"> Hello React! </h1>
this compiles to
const heading = React.createElement('h1',{className: 'site-heading'}, 'Hello React!')
const name = 'Chandan'
const heading = <h1> Hello, {name}</h1>
Props vs. State
class Welcome extends React.Component{
render(){
return(
<h1> Hello {this.props.name} </h1>
);
}
}
const element = <Welcome name="Chandan" />;
// rewriting the above component to be simpler
function Welcome(props){
return <h1> Hello {props.name}</h1>;
}
- A component can also have default props, so if a prop isn't passed through it can still be set. We can make the
name
property optional by addingdefaultProps
to theWelcome
class:
class Welcome extends React.Component{
render(){
return(
<h1> Hello {this.props.name} </h1>
);
}
}
Welcome.defaultProps = {
name: "world",
};
-
During a component's lifecycle
props
should not change (consider them immutable) -
Since
props
are passed in, and they cannot change, you can think of any React component that only usesprops
(and notstate
) as "pure", that is, it will always render the same output given the same input. This makes them really easy to test.
State
-
By default, a component has no state. The
Welcome
component from above is stateless. -
We should use
state
when a component needs to keep track of information between renderings the component itself can create, update, and use state.
// An example that keeps track of how many times you have clicked the button
class Button extends React.Component{
constructor(props){
super(props);
this.state = {
count: 0, // The initial data can be hard coded or come from 'props'
};
}
updateCount(){
this.setState((prevState, props) => {
return { count: prevState.count + 1}
});
}
render(){
return(
<button onClick={() => this.updateCount()}>
Clicked {this.state.count} times
</button>
);
}
}
setState
takes a function becausesetState
can run asynchronously. It needs to take a callback function rather than updating the state directly.
Warning 1 - Always use
setState
notthis.state.count = this.state.count + 1
Warning 2 -
this.setState({
count: this.state.count + 1 //As I used in ButtonToggle.jsx
});
Although this might look reasonable, doesn't throw errors, and you might find examples that use this syntax online, it is wrong. This does not take into account the asynchronous nature that 'setState' can use and might cause errors with out of sync state data.
onClick={() => this.updateCount()}
- Need to use ES6's arrow function so updateCount will have access to this instance's state.
-
props
contains information set by the parent component (although defaults can be set) and should not changed -
state
contains "private" information for the component to initialize, change, and use on it's own. -
Props
are a way of passing data from parent to child.State
is reserved only for interactivity, that is, data that changes over time.
State and Lifecycle
- To update state, use
setState()
this.setState({comment: 'Hello'});
- 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:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
Shallow Merging
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
});
});
}
The merging is shallow, so
this.setState({comments})
leavesthis.state.posts
intact, but completely replacesthis.state.comments
Handling Events
- In JavaScript, class methods are not
bound
by default. If you forget to bindthis.handleClick
and pass it toonClick
,this
will beundefined
when the function is actually called.
This is not React-specific behavior; it is a part of how function work in javascript. Generally, you refer to a method without
()
after it, such asonClick={this.handleClick}
, you should bind that method.
Passing Arguments to Event Handlers
<button onClick={this.deleteRow.bind(this,id)}> Delete Row </button>
Conditional Rendering
if(isLoggedIn){
button = <LogoutButton onClick={this.handleLogoutClick} />;
}
else{
button = <LoginButton onClick={this.handleLoginClick} />;
}
Inline If with Logical && Operator
- You may embed any expressions in JSX by wrapping them in curly braces.
<div>
<h2> Next line will only execute if the condition satisfies. </h2>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
-
It works because in JavaScript,
true && expression
always evaluates toexpression
, andfalse && expression
always evaluates tofalse
. -
Therefore, if the condition is
true
, the element right after&&
will appear in the output. If it isfalse
, React will ignore and skip it.
Inline If-Else with Conditional Operator
- Another method for conditionally rendering elements inline is to use the JavaScript conditional operator
condition ? true : false
render(){
const isLoggedIn = this.state.isLoggedIn;
return(
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
It can also be used for larger expressions -
render(){
const isLoggedIn = this.state.isLoggedIn;
return(
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
List and Keys
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number*2);
console.log(doubled);
- Keys help React identify which items have changes, are added, or are removed.
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
- Correct
key
usage - elements inside themap()
call need keys
function ListItem(props){
//Correct! There is no need to specify the key here:
return (
<li>{props.value}</li>
);
}
function NumberList(props){
const Numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()} value = {number} />
);
return(
<ul> {listItems} </ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList nubmers={numbers} />, document.getElementById('root')
);
- Keys used within arrays should be unique among their siblings. However they don't need to be globally unique. We can use the same keys when we produce two different arrays:
function Blog(props){
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return(
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />, document.getElementById('root')
);
- Keys serve as a hint to React but they don't get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name:
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title}
/>
);
Forms
-
In React, a
<textarea>
uses avalue
attribute instead. This way, a form using a<textarea>
can be written very similarly to a form that uses a single-line input. -
React, instead of using
selected
attribute, uses avalue
attribute on the rootselect
tag. This is more convenient in a controlled component because you only need to update it in one place.
Overall, this makes it so that
<input type="text">
,<textarea>
, and<select>
all work very similarly - they all accept avalue
attribute that you can use to implement a controlled component.
- You can pass an array into the
value
attribute, allowing you to select multiple options in aselect
tag:
<select multiple={true} value={['B', 'C']}>
setState()
automatically merges a partial state into the current state, we only need to call it with the changed parts.
Controlled Input Null Value
- Specifying the value prop on a "controlled component" prevents the user from changing the input unless you desire so. If you're specified a
value
but the input is still editable, you may have accidentally setvalue
toundefined
ornull
.
In the following code the input is locked at first but become editable after a short delay -
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function(){
ReactDOM.render(<input value={null} />, mountNode);
},1000);
- Full-fledged React form solution including validation, keeping track of the visited fields and handling form submission - Formik
Lifting State Up
-
In React, sharing state is accomplished by moving it up to the closest common ancestor of the components that need it. This is called "lifting state up".
-
There should be a single "source of truth" for any data that changes in a React application. Usually, the state is first added to the component that needs rendering. Then, if other component also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on the 'top-down data flow'.
- To insert a variable in the output text -
alert(`Welcome aboard, ${this.state.login}!`);
Thinking In React
-
Single Responsibility Principle - A component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller sub-components.
-
UI and data models tend to adhere to the same information architecture. Separate your UI into components where each component matches one piece of your data model.
-
React's one-way data flow (also called one-way binding) keeps everything modular and fast.