A crash course guide on the important concepts within the React library
Course
- What is React?
- JSX (Javascript XML)
- The Virtual DOM
- Component Architecture
- Class Components
- Functional Components
- Event Handlers
- Binding Event Handlers
- Props
- Children Props
- State
- Lifecycle Methods
- Mounting
- Updating
- Unmounting
- React Router
- Getting Started
- Routes and Paths
- Links
- Route Props
- Route Parameters
- Redirect
- Debugging: React Developer Tools Chrome Extension
What is React?
React is a JavaScript UI library that follows a component-based approach to allow reusability of code.
Create React App is a comfortable environment for learning React and is the best way to start building a new single page application in React. To use it run:
npx create-react-app <my-app>
The starting setup to React typically looks like this:
//index.html
<div id="root"></div>
//index.js
import React from "react"
import ReactDOM from "react-dom"
import App from "./App" //This is a component that we will go over later
ReactDOM.render(<App/>, document.getElementById("root"))
JSX (Javascript XML)
JSX is a syntax extension of JavaScript that allows you to combine HTML code with JavaScript. To add JavaScript to HTML tags, put your JavaScript code in curly braces {}. For example:
const user = {firstName: 'John', lastName: 'Doe'};
const element = ( <h1> Hello, {user.firstName}! </h1>)
//Notice how we combined Javascript with HTML here
Babel is a JavaScript library that compiles the JSX language into JavaScript that the browser can understand.
In React, we use the className attribute to make changes to the design of elements.
The Virtual DOM
React uses Virtual DOM exists which is like a lightweight copy of the actual DOM(a virtual representation of the DOM). So for every object that exists in the original DOM, there is an object for that in React Virtual DOM. It is exactly the same, but it does not have the power to directly change the layout of the document. Manipulating DOM is slow, but manipulating Virtual DOM is fast as nothing gets drawn on the screen. So each time there is a change in the state of our application, the virtual DOM gets updated first instead of the real DOM.
React contains two virtual DOM, one that is pre-updated and one that is updated. It then compares the two to see what changed and instead of reloading the page on change, it makes changes to only the document object.
Component Architecture
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
Class Based Components
Class components in React are similar to classes in object-oriented programming, where the component can extend from a parent class and a state can be created along with several methods. However, React requires you to render HTML code within a class by using the render method. On top of that, React has it's own built in methods called lifecycle hooks which we will take a look at later.
To create a Class Component, you use class (Name) extends React.Component{}. An example of a Class Component looks something like this:
class Name extends React.Component {
state = {
firstName: 'John'
}
render() {
return (
<div> Hello {this.state.firstName} </div>
);
}
}
One important thing to note when rendering HTML elements is that you have to wrap the elements in one main div tag. An alternative to this is using React.Fragment so that the main div tag doesn't get rendered in the actual HTML code.
render() {
return (
<React.Fragment>
<h1> Hello {this.state.firstName} </h1>
</React.Fragment>
);
}
Functional Components
You can also create components by creating them similar to how you create functions followed by a naming convention where we capitalize the name of the component. Keep in mind that functional components are also known as dummy components because they have no state. To create a functional component, you use the arrow syntax in Javascript.
const Home = () => {
return <h1> Hello World </h1>;
};
Props
Props are data we give to a component and they are Read-Only. Keep in mind that components can have other components which create a tree-like structure, so you can pass down data from component to component.
//Products
class Products extends Component {
state = {
product: { id: 1, name: "Product 1" },
}
render() {
return (
<div>
<h1>Products</h1>
<Product key={product.id} name={product.name}/>
//We are passing down product id and name to the Product component
</div>
);
}
}
Once the props are passed down, we can call them using this.props. Ideally we want to use the object destructuring syntax to avoid repeating the this.props.
//Product Detail Component
class Product extends Component {
const {name} = this.props
render() {
return (
<div>
<h1> {name} </h1>
</div>
);
}
}
We can also pass content between tags known as children.
//Products
class Products extends Component {
state = {
product: { id: 1, name: "Product 1" },
};
render() {
return (
<div>
<Product key={product.id} name={product.name}>
<h4>Title</h4>
</Product>
//We are passing down the h4 tag as a child to the component which can be called using this.props.children in the child component
</div>
);
}
}
There's a chance that your state will have a lot of properties that need to be passed down but it's tedious to write the code to pass them all down. In this case, you can use
the state that you want to pass down to include the rest of the object properties.
class Products extends Component {
state = {
product: {
id: 1,
name: "Product 1",
price: $10,
reviews: ["5", "3"]
},
};
render() {
return (
<div>
<Product key={product.id} product={product}></Product>
//To access name, price, and reviews in the child component, you'd have to call this.props.product.name, etc
</div>
);
}
}
In class components we use the this keyword when access props, but for stateless components we don't need to use that keyword. This is because stateless React components pass props to functional components during runtime. So instead, we update the params to be props and access it by using props followed by the property name.
const Product = (props) => {
return (
<div>
<h1>{props.name}</h1>
</div>
);
};
//We can also destructure like this
const Product = ({name}) => {
return (
<div>
<h1>{name}</h1>
</div>
);
};
State
The state of a class includes data that is local to the component, which can be passed down as props to children components.
When it comes to states however, there should be only one single source of truth. That being said, the component that owns a piece of state should be the one changing it.
You can initialize a state in the constructor of a class using this.state = {} but if there isn't a constructor don't have to use the this keyword. For example:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
When we make a change to the state, we shouldn't update the state directly. Instead, we should use the function setState()
// Wrong
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. To fix it, 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:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
Always make sure to create a clone of the state rather than touch it directly. If you update the data of the state directly, the value changes but the state appears to look the same.
To understand more about the state of components, we need to take a look at lifecycle methods in React, which you'll learn about in the next section. There's also a way to add state to dummy components using React Hooks, but you can read more about that in a seperate article.
Lifecycle Methods
Lifecycle Methods are used only in Class components to free up resources. We have three main lifecycle methods: Mounting, Updating, Unmounting.
Mounting
When we mount, an instance of a component is created and inserted to the DOM. Afterwards, three hooks get called in order:
1. constructor()
2. render()
3. componentDidMount()
this new componentDidMount() method adds new values to the state after the component is rendered and initializes this as the first state of the application. This is especially useful when making Ajax calls because we initially have no data and we render HTML, but after getting our data we update the state with the data.
componentDidMount() {
// Changing the state after 2 sec
// from the time when the component
// is rendered
setTimeout(() => {
this.setState({ color: 'wheat' });
}, 2000);
}
Updating
To update our component's state with new data or props we run componentDidUpdate(). To determine if anything changed however, we pass in the arguments prevProps or prevState. If the current state is different than the prevState, we render the new change.
componentDidUpdate(prevProps, prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
Unmounting
To free up resources and avoid memory leakage, we unmount our component which basically removes it from the DOM along with all its data. To do this we use componentWillUnmount().
Read more about lifecycle methods here: React.Component – React (reactjs.org)
Event Handlers
Event handlers are used to trigger an event and are similar to handling events on DOM elements with some changes in syntax. Common event handlers are onChange, onClick, onSubmit.
With JSX you pass in a function rather than a string.
//HTML and DOM
<button onclick="activateLasers()">
Activate Lasers
</button>
//React with JSX
<button onClick={activateLasers}>
Activate Lasers
</button>
We use event handlers to change a state and typically we follow a structure like this:
1. Add a method to the main component which changes the state
2. Pass it in as props to the next component.
3. The child component calls the handler.
Binding
You'll need to bind methods to the class to be able to use the this keyword.
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>
);
}
}
There are two ways to get around this bind syntax, but binding is recommended due to increased performance.
// Method 1: Change method to arrow function
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
handleClick = () => {
console.log('this is:', this);
};
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
// Method 2: Call handler using arrow syntax
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
Passing Arguments to EventHandlers
//Method 1: Arrow Syntax
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
//Method 2: Function is Binded
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
Rendering
Conditional Rendering
You can choose to render two different JSX piece of code based on conditions.
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
Rendering Lists
Keys are essential when rendering lists or else you will get a warning. They are necessary to boost performance, but the key data must be unique though.
Use the map() method to create multiple components.
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
Forms
HTML form elements work a bit differently from other DOM elements in React, because form elements naturally keep some internal state. For example, this form in plain HTML accepts a single name:
Typically, for forms in React we want to initialize the form state properties as empty strings. This creates a single source of truth.
state = {
data: {
title: "",
genreId: "",
numberInStock: "",
dailyRentalRate: ""
},
preventDefault() prevents default behavior like when a page reloads after form is submitted.
//HTML
<form onsubmit="console.log('You clicked submit.'); return false">
<button type="submit">Submit</button>
</form>
//React
function Form() {
function handleSubmit(event) {
event.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
We get the text within the input using event.target.value(). This is what a form component can look like:
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>
);
}
}
React Router
Getting Started
React Router allows your components to below to certain URLs.
To install run this:
npm i react-router-dom
We use BrowserRouter to connect our app to the browser, but we can also use Router.
...
import { BrowserRouter } from "react-router-dom"; // or Router
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
Routes and Paths
For children components we use Route. The attributes of this are the path that the component is part of and the component itself.
import { Route } from "react-router-dom";
<Route path="/products" component={Product}/>
You have to be careful with paths because all paths will render at the same time if they have a common path like "/". To fix this, you can either use the exact keyword or a Switch statement.
Also, remember to order your Routes from most specific to most generic.
Using the exact keyword makes sure the Route follows the exact pathing:
<Route path="/" exact component={Home} />
Using the Switch Statement:
import { Route, Switch} from "react-router-dom";
...
<div className="content">
<Switch>
<Route path="/products/:id" component={ProductDetails} />
<Route path="/products" component={Product}/>
<Route path="/" exact component={Home} />
</Switch>
</div>
Links
Replace <a> tags with Link to prevent page reloads and allow for single page applications. The Link tag uses the attribute to instead of href.
import { Link } from "react-router-dom";
const NavBar = () => {
return (
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/products">Products</Link>
</li>
<li>
<Link to="/admin">Admin</Link>
</li>
</ul>
);
};
Route Props
When we wrap a component with Route, we inject certain props such as history, location, match, pathname, etc.
Sometimes we want to inject both the route props and the props from the component itself. To do this, we use the render attribute and use an arrow function which returns the component.
//To pass in props of the component only
<Route
path="/products"
render={() => <Products sortBy="newest" />}
/>
//To pass in BOTH the router props and component props
<Route
path="/products"
render={props => <Products sortBy="newest" {...props} />}
/>
Route Parameters
Routes can have parameters if their paths have semicolons followed by the property required.
<Route path="/products/:id" component={ProductDetails} />
To make optional params, add a question mark at the end of the path. Try to avoid doing this and instead use query strings.
<Route path="/products/:id?" component={ProductDetails} />
To make a path which involves a query, install the query string package and create the query string using the location prop.
npm i query-string@6.1.0
import queryString from "query-string"
queryString.parse(location.search)
Redirect
We can use redirect for a page not found or if you want to have the user go to a page when typing in a different URL.
import { Redirect } from "react-router-dom";
<Redirect from="/messages" to="/posts" />
<Redirect to="/not-found" />
We can use redirect at the end of our routing so that the browser can check all the previous routes before sending the user to a 404 page.
When we submit a form, we want a redirect called programmatic navigation which sends the user to a certain page after sending data. To do this, we can use the history prop and push method which triggers an event, sending the user to the path sent in the argument.
//Sends user to the products page after they log in
this.props.history.push('/products")
We can also use replace for logins so that the user can't go back to login page.
this.props.history.replace('/products")
React Chrome Debugger
You can debug React very easily by downloading the React Developer Tools in Chrome Extensions.
ความคิดเห็น