React JS A Step-by-Step Guide. Learn Hooks and States.
React JS A Step-by-Step Guide. Learn Hooks and States.
A Step-by-Step Guide to
Mastering the Top Web
Development Library from
Basic to Advanced Level
Tim Simon
© Copyright 2024 - All rights reserved.
No portion of this book may be reproduced in any form
without written permission from the publisher or author,
except as permitted by U.S. copyright law.
Legal Notice:
This book is copyright protected. This is only for personal
use. You cannot amend, distribute, sell, use, quote or
paraphrase any part or the content within this book without
the consent of the author.
Disclaimer Notice:
This publication is designed to provide accurate and
authoritative information in regard to the subject matter
covered. It is sold with the understanding that neither the
author nor the publisher is engaged in rendering legal,
investment, accounting or other professional services. While
the publisher and author have used their best efforts in
preparing this book, they make no representations or
warranties with respect to the accuracy or completeness of
the contents of this book and specifically disclaim any
implied warranties of merchantability or fitness for a
particular purpose. No warranty may be created or
extended by sales representatives or written sales
materials. The advice and strategies contained herein may
not be suitable for your situation. You should consult with a
professional when appropriate. Neither the publisher nor the
author shall be liable for any loss of profit or any other
commercial damages, including but not limited to special,
incidental, consequential, personal, or other damages.
Table of Contents
Introduction
Diving into JSX
Components in React
State and Props
Advanced Component Features
Handling Events and Forms
Routing with React
State Management with Redux
Testing in React
React Performance Optimization
Real-World Applications and Best Practices
Conclusions
Introduction
Why is React.js?
1. Efficiency and Performance
2. Reusable Components
Components are the building blocks of any React
application. Each component has its own logic and controls
its own rendering, and can be reused throughout the
application. This reusability not only makes the code more
manageable but also helps in scaling the application with
ease.
6. Ease of Learning
Why JSX?
Components
Components are the building blocks of any React
application. They encapsulate elements of the UI and the
associated logic in an isolated and reusable manner. In
React, components can be broadly classified into two types:
State
State in React is a structure that holds some information
that may change over the lifecycle of a component. It is
pivotal in creating dynamic and interactive components.
Props
Short for "properties," props in React are read-only objects
which must be kept pure. They are used to pass data from
one component to another, particularly from parent to child
components. Props are fundamental for component
reusability and composition.
a) Download Node.js
b) Verify Installation
Run node -v and npm -v. These commands should return the
installed versions of Node.js and npm, respectively.
1.
const name = 'React Developer';
2.
const element = <h1>Hello, {name}</h1>;
1.
// JSX
2.
const element = <div className="container">Content</div>;
3.
4.
// HTML
5.
<div class="container">Content</div>
Components in JSX
JSX is not limited to standard HTML elements. It can also
represent React components, whether they are functional or
class-based.
Example of Component in JSX:
1.
// Define a component
2.
function Welcome(props) {
3.
return <h1>Hello, {props.name}</h1>;
4.
}
5.
6.
// Use the component in JSX
7.
const element = <Welcome name="React Developer" />;
1.
function handleClick() {
2.
console.log('Button clicked');
3.
}
4.
5.
const element = <button onClick={handleClick}>Click
me</button>;
1.
function WelcomeMessage({ isLoggedIn }) {
2.
return (
3.
<div>
4.
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.
</h1>}
5.
</div>
6.
);
7.
}
1.
const items = ['Apple', 'Banana', 'Cherry'];
2.
const listItems = items.map((item) => <li key={item}>{item}
</li>);
3.
4.
return <ul>{listItems}</ul>;
1.
<!-- HTML -->
2.
<div class="container">
3.
<h1>Hello, world!</h1>
4.
</div>
1.
// JSX
2.
<div className="container">
3.
<h1>Hello, world!</h1>
4.
</div>
1.
<!-- HTML -->
2.
<input type="text" onclick="handleClick()" />
1.
// JSX
2.
<input type="text" onClick={handleClick} />
1.
<!-- HTML -->
2.
<div class="container"></div>
1.
// JSX
2.
<div className="container"></div>
1.
// JSX (dynamic)
2.
<p>Today's date: {new Date().toLocaleDateString()}</p>
1.
// Define a custom component
2.
function Welcome(props) {
3.
return <h1>Hello, {props.name}</h1>;
4.
}
5.
6.
// Use the component in JSX
7.
const element = <Welcome name="React Developer" />;
1.
<!-- HTML -->
2.
<button onclick="handleClick()">Click me</button>
1.
// JSX
2.
<button onClick={handleClick}>Click me</button>
1.
// JSX (strict)
2.
<br />
3.
<img src="image.jpg" />
1.
const name = 'React Developer';
2.
const greeting = <h1>Hello, {name}</h1>;
1.
function formatName(user) {
2.
return user.firstName + ' ' + user.lastName;
3.
}
4.
5.
const user = {
6.
firstName: 'John',
7.
lastName: 'Doe'
8.
};
9.
10.
const element = <h1>Hello, {formatName(user)}!
</h1>;
1.
function WelcomeMessage({ isLoggedIn }) {
2.
return <div>{isLoggedIn ? <h1>Welcome back!</h1> :
<h1>Please sign in.</h1>}</div>;
3.
}
Embedding Styles
Inline styles in JSX are written as JavaScript objects, offering
a more dynamic approach to styling.
Example of Embedding Styles:
1.
const divStyle = {
2.
color: 'blue',
3.
backgroundColor: 'lightgray',
4.
};
5.
6.
const element = <div style={divStyle}>Styled Text</div>;
1.
const element = (
2.
<div>
3.
{/* This is a comment in JSX */}
4.
<h1>Hello, world!</h1>
5.
</div>
6.
);
5.
6.
// Avoid
7.
function UserInfo() {
8.
// ...
9.
}
5.
6.
// In JSX
7.
<div className="container">Content</div>
4.
5.
// Avoid
6.
return <div>{isLoggedIn ? 'Welcome back!' : 'Please sign in.'}
</div>;
1.
const users = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
2.
return (
3.
<ul>
4.
{users.map(user => (
5.
<li key={user.id}>{user.name}</li>
6.
))}
7.
</ul>
8.
);
1.
return (
2.
<div>
3.
{isLoggedIn && <LogoutButton />}
4.
{!isLoggedIn && <LoginButton />}
5.
</div>
6.
);
2.
3.
class Welcome extends Component {
4.
render() {
5.
return <h1>Hello, {this.props.name}</h1>;
6.
}
7.
}
1.
function Welcome(props) {
2.
return <h1>Hello, {props.name}</h1>;
3.
}
1.
function Greeting() {
2.
return <h1>Welcome to React!</h1>;
3.
}
Props in Components
Props (short for properties) are read-only data passed from a
parent component to a child component. They are an
essential aspect of React components, allowing them to be
dynamic and versatile.
Example of Using Props:
1.
function UserGreeting(props) {
2.
return <h1>Welcome, {props.username}!</h1>;
3.
}
1.
class Counter extends Component {
2.
constructor(props) {
3.
super(props);
4.
this.state = { count: 0 };
5.
}
6.
7.
incrementCount = () => {
8.
this.setState({ count: this.state.count + 1 });
9.
}
10.
11.
render() {
12.
return (
13.
<div>
14.
<p>Count: {this.state.count}</p>
15.
<button onClick=
{this.incrementCount}>Increment</button>
16.
</div>
17.
);
18.
}
19.
}
1.
import React, { useState } from 'react';
2.
3.
function Counter() {
4.
const [count, setCount] = useState(0);
5.
6.
return (
7.
<div>
8.
<p>Count: {count}</p>
9.
<button onClick={() => setCount(count +
1)}>Increment</button>
10.
</div>
11.
);
12.
}
Here, Counter is a functional component that uses the
useState hook to manage its count state.
Class Components vs. Functional Components
In React.js, components come in two distinct types: Class
components and Functional components. Understanding the
differences between these two types of components is
crucial for React developers, as it influences not only the
way components are written but also how they manage
state, lifecycle methods, and side effects.
Class Components
Class components are more traditional in React. They are
ES6 classes that extend from React.Component and offer
more features compared to functional components,
particularly in managing state and lifecycle.
Characteristics of Class Components:
1.
import React, { Component } from 'react';
2.
3.
class Welcome extends Component {
4.
constructor(props) {
5.
super(props);
6.
this.state = { greeting: 'Hello' };
7.
}
8.
9.
render() {
10.
return <h1>{this.state.greeting},
{this.props.name}</h1>;
11.
}
12.
}
2.
3.
function Welcome({ name }) {
4.
const [greeting, setGreeting] = useState('Hello');
5.
6.
return <h1>{greeting}, {name}</h1>;
7.
}
1.
import React, { useState, useEffect } from 'react';
2.
3.
function User({ userID }) {
4.
const [user, setUser] = useState(null);
5.
6.
useEffect(() => {
7.
fetchData(userID).then(data => setUser(data));
8.
}, [userID]);
9.
10.
return (
11.
<div>
12.
{user ? <p>{user.name}</p> : <p>Loading...
</p>}
13.
</div>
14.
);
15.
}
Updating Phase
Updating occurs when the component's props or state
change. The following methods are called in this phase:
1. static getDerivedStateFromProps(props, state):
1.
shouldComponentUpdate(nextProps, nextState) {
2.
return nextProps.id !== this.props.id;
3.
}
3. render():
Unmounting Phase
Unmounting is the final phase of a component's lifecycle
when it is being removed from the DOM:
1. componentWillUnmount():
1.
class User extends React.Component {
2.
constructor(props) {
3.
super(props);
4.
this.state = { user: null };
5.
}
6.
7.
componentDidMount() {
8.
this.fetchUserData(this.props.userID);
9.
}
10.
11.
componentDidUpdate(prevProps) {
12.
if (this.props.userID !== prevProps.userID) {
13.
this.fetchUserData(this.props.userID);
14.
}
15.
}
16.
17.
componentWillUnmount() {
18.
// Cleanup logic here
19.
}
20.
21.
fetchUserData(userID) {
22.
// Fetch logic here
23.
}
24.
25.
render() {
26.
// Rendering UI here
27.
}
28.
}
1.
import React, { useState } from 'react';
2.
3.
function Counter() {
4.
const [count, setCount] = useState(0);
5.
6.
return (
7.
<div>
8.
<p>You clicked {count} times</p>
9.
<button onClick={() => setCount(count + 1)}>
10.
Click me
11.
</button>
12.
</div>
13.
);
14.
}
1.
import React, { useState, useEffect } from 'react';
2.
3.
function User({ userID }) {
4.
const [user, setUser] = useState(null);
5.
6.
useEffect(() => {
7.
const fetchData = async () => {
8.
const response = await fetch('https://api.example.com/user/' +
userID);
9.
const userData = await response.json();
10.
setUser(userData);
11.
};
12.
13.
fetchData();
14.
}, [userID]);
15.
16.
return (
17.
<div>
18.
{user ? <p>{user.name}</p> : <p>Loading...
</p>}
19.
</div>
20.
);
21.
}
2.
3.
function Timer() {
4.
const [seconds, setSeconds] = useState(0);
5.
6.
useEffect(() => {
7.
const intervalId = setInterval(() => {
8.
setSeconds(seconds => seconds + 1);
9.
}, 1000);
10.
11.
return () => clearInterval(intervalId); // Cleanup
interval on unmount
12.
}, []);
13.
14.
return <p>Timer: {seconds} seconds</p>;
15.
}
2.
3.
function Counter() {
4.
const [count, setCount] = useState(0);
5.
6.
return (
7.
<div>
8.
<p>You clicked {count} times</p>
9.
<button onClick={() => setCount(count + 1)}>
10.
Increment
11.
</button>
12.
</div>
13.
);
14.
}
1.
import React, { Component } from 'react';
2.
3.
class Counter extends Component {
4.
constructor(props) {
5.
super(props);
6.
this.state = { count: 0 };
7.
}
8.
9.
incrementCount = () => {
10.
this.setState(prevState => ({
11.
count: prevState.count + 1
12.
}));
13.
}
14.
15.
render() {
16.
return (
17.
<div>
18.
<p>You clicked {this.state.count} times</p>
19.
<button onClick={this.incrementCount}>
20.
Increment
21.
</button>
22.
</div>
23.
);
24.
}
25.
}
In this class component, the state is initialized in the
constructor and updated using this.setState.
Managing State in Functional Components with
Hooks
With the introduction of hooks, functional components can
now manage state using the useState hook.
Benefits of useState Hook:
1.
import React, { useState } from 'react';
2.
3.
function ParentComponent() {
4.
const [sharedState, setSharedState] =
useState(0);
5.
6.
return (
7.
<div>
8.
<ChildComponentOne
sharedState={sharedState} />
9.
<ChildComponentTwo
setSharedState={setSharedState} />
10.
</div>
11.
);
12.
}
13.
14.
function ChildComponentOne({
sharedState }) {
15.
return <div>Shared State:
{sharedState}</div>;
16.
}
17.
18.
function ChildComponentTwo({
setSharedState }) {
19.
return <button onClick={() =>
setSharedState(state => state +
1)}>Increment</button>;
20.
}
1.
function Welcome(props) {
2.
return <h1>Hello, {props.name}
</h1>;
3.
}
4.
5.
const App = () => {
6.
return <Welcome name="React
Developer" />;
7.
}
1.
function UserInfo({ user, onClick }) {
2.
return (
3.
<div>
4.
<h1>{user.name}</h1>
5.
<p>{user.age}</p>
6.
<button onClick={onClick}>Click
Me</button>
7.
</div>
8.
);
9.
}
10.
11.
const user = { name: 'Jane Doe', age: 30
};
12.
13.
const App = () => {
14.
const handleClick = () => {
15.
console.log('Button clicked');
16.
};
17.
18.
return <UserInfo user={user} onClick=
{handleClick} />;
19.
}
1.
UserInfo.defaultProps = {
2.
user: { name: 'Default User', age: 0 },
3.
};
1.
import PropTypes from 'prop-types';
2.
3.
UserInfo.propTypes = {
4.
user: PropTypes.shape({
5.
name: PropTypes.string.isRequired,
6.
age: PropTypes.number.isRequired,
7.
}),
8.
onClick: PropTypes.func,
9.
};
1.
function User({ user }) {
2.
return (
3.
<div>
4.
<UserProfile user={user} />
5.
<UserPosts userId={user.id} />
6.
</div>
7.
);
8.
}
1.
function Greeting({ message }) {
2.
return <h1>{message}</h1>;
3.
}
Class Components:
1.
class Greeting extends
React.Component {
2.
render() {
3.
return <h1>{this.props.message}
</h1>;
4.
}
5.
}
1.
function BoilingVerdict({ celsius }) {
2.
return <p>{celsius >= 100 ? 'The
water would boil.' : 'The water would not boil.'}
</p>;
3.
}
4.
5.
function TemperatureInput({
temperature, onTemperatureChange }) {
6.
return (
7.
<div>
8.
<label>
9.
Enter temperature in Celsius:
10.
<input
11.
type="text"
12.
value={temperature}
13.
onChange={(e) =>
onTemperatureChange(e.target.value)}
14.
/>
15.
</label>
16.
</div>
17.
);
18.
}
1.
import React, { useState } from 'react';
2.
3.
function Calculator() {
4.
const [temperature, setTemperature]
= useState('');
5.
6.
function
handleTemperatureChange(temperature) {
7.
setTemperature(temperature);
8.
}
9.
10.
return (
11.
<div>
12.
<TemperatureInput
13.
temperature={temperature}
14.
onTemperatureChange=
{handleTemperatureChange}
15.
/>
16.
<BoilingVerdict celsius=
{parseFloat(temperature)} />
17.
</div>
18.
);
19.
}
In this setup, Calculator holds the shared state temperature.
It passes the state down to TemperatureInput and
BoilingVerdict as props. The TemperatureInput component
receives a function onTemperatureChange as a prop to
update the state in Calculator.
Key Points in Lifting State Up
1.
function
withAdditionalData(WrappedComponent) {
2.
return function(props) {
3.
const extraData = 'Extra Data';
4.
return <WrappedComponent
extraData={extraData} {...props} />;
5.
};
6.
}
1.
function MyComponent({ extraData })
{
2.
return <div>{extraData}</div>;
3.
}
4.
5.
const EnhancedComponent =
withAdditionalData(MyComponent);
6.
7.
// EnhancedComponent now has access
to `extraData`
1.
function
withLoadingIndicator(WrappedComponent) {
2.
return function({ isLoading, ...props
}) {
3.
if (isLoading) {
4.
return <div>Loading...</div>;
5.
}
6.
return <WrappedComponent
{...props} />;
7.
};
8.
}
9.
10.
function MyList({ items }) {
11.
return (
12.
<ul>
13.
{items.map(item => <li key={item}>
{item}</li>)}
14.
</ul>
15.
);
16.
}
17.
18.
const MyListWithLoadingIndicator =
withLoadingIndicator(MyList);
1.
class MouseTracker extends
React.Component {
2.
constructor(props) {
3.
super(props);
4.
this.state = { x: 0, y: 0 };
5.
}
6.
7.
handleMouseMove = (event) => {
8.
this.setState({
9.
x: event.clientX,
10.
y: event.clientY
11.
});
12.
}
13.
14.
render() {
15.
return (
16.
<div style={{ height: '100vh' }}
onMouseMove={this.handleMouseMove}>
17.
{this.props.render(this.state)}
18.
</div>
19.
);
20.
}
21.
}
1.
function App() {
2.
return (
3.
<MouseTracker render={({ x, y })
=> (
4.
<h1>The mouse position is ({x},
{y})</h1>
5.
)} />
6.
);
7.
}
1.
class DataFetcher extends
React.Component {
2.
state = { data: null, isLoading: true
};
3.
4.
componentDidMount() {
5.
fetch(this.props.url)
6.
.then(response => response.json())
7.
.then(data => this.setState({ data,
isLoading: false }));
8.
}
9.
10.
render() {
11.
return this.props.render(this.state);
12.
}
13.
}
14.
15.
function App() {
16.
return (
17.
<DataFetcher
18.
url="https://api.example.com/data"
19.
render={({ data, isLoading }) => {
20.
if (isLoading) return <div>Loading...
</div>;
21.
return <div>{JSON.stringify(data)}
</div>;
22.
}}
23.
/>
24.
);
25.
}
In this DataFetcher component, the data fetching logic is
encapsulated and the rendering is delegated to the App
component using a render prop.
Best Practices and Considerations
1.
class ErrorBoundary extends
React.Component {
2.
constructor(props) {
3.
super(props);
4.
this.state = { hasError: false };
5.
}
6.
7.
static getDerivedStateFromError(error)
{
8.
// Update state to render fallback UI
9.
return { hasError: true };
10.
}
11.
12.
componentDidCatch(error, errorInfo) {
13.
// Log error information
14.
console.error("Error caught by Error
Boundary: ", error, errorInfo);
15.
}
16.
17.
render() {
18.
if (this.state.hasError) {
19.
// Custom fallback UI
20.
return <h1>Something went wrong.
</h1>;
21.
}
22.
23.
return this.props.children;
24.
}
25.
}
In this ErrorBoundary component, when an error is caught,
the state is updated to display a fallback UI.
Using Error Boundaries in React
To use an error boundary, wrap it around any component
that might throw an error. It’s common to place error
boundaries at the top level of the application or around
certain high-risk component areas.
Example of Using an Error Boundary:
1.
function MyComponent() {
2.
return (
3.
<ErrorBoundary>
4.
<ComponentThatMayThrowError />
5.
</ErrorBoundary>
6.
);
7.
}
1.
import React from 'react';
2.
3.
const UserContext = React.createContext();
4.
5.
export default UserContext;
1.
import React from 'react';
2.
import UserContext from
'./UserContext';
3.
4.
class App extends React.Component {
5.
state = {
6.
user: 'John Doe',
7.
};
8.
9.
render() {
10.
return (
11.
<UserContext.Provider value=
{this.state.user}>
12.
<ChildComponent />
13.
</UserContext.Provider>
14.
);
15.
}
16.
}
1.
import React from 'react';
2.
import UserContext from
'./UserContext';
3.
4.
class ChildComponent extends
React.Component {
5.
render() {
6.
return (
7.
<UserContext.Consumer>
8.
{user => <div>Logged in user:
{user}</div>}
9.
</UserContext.Consumer>
10.
);
11.
}
12.
}
1.
import React, { useContext } from
'react';
2.
import UserContext from
'./UserContext';
3.
4.
function ChildComponent() {
5.
const user =
useContext(UserContext);
6.
return <div>Logged in user: {user}
</div>;
7.
}
1.
class ClickButton extends
React.Component {
2.
handleClick() {
3.
console.log('Button clicked');
4.
}
5.
6.
render() {
7.
return <button onClick=
{this.handleClick}>Click me</button>;
8.
}
9.
}
1.
class ClickButton extends
React.Component {
2.
constructor(props) {
3.
super(props);
4.
this.handleClick =
this.handleClick.bind(this);
5.
}
6.
7.
handleClick() {
8.
console.log('Button clicked');
9.
}
10.
11.
render() {
12.
return <button onClick=
{this.handleClick}>Click me</button>;
13.
}
14.
}
1.
class ClickButton extends React.Component {
2.
handleClick = () => {
3.
console.log('Button clicked');
4.
}
5.
6.
render() {
7.
return <button onClick={this.handleClick}>Click me</button>;
8.
}
9.
}
Event Arguments
If you need to pass arguments to an event handler, you can
use an arrow function in the callback.
Example of Passing Arguments:
1.
class ClickButton extends
React.Component {
2.
handleClick(id) {
3.
console.log('Button clicked, ID:', id);
4.
}
5.
6.
render() {
7.
return <button onClick={() =>
this.handleClick(1)}>Click me</button>;
8.
}
9.
}
1.
function ClickButton() {
2.
function handleClick() {
3.
console.log('Button clicked');
4.
}
5.
6.
return <button onClick=
{handleClick}>Click me</button>;
7.
}
Best Practices
1.
class ContactForm extends
React.Component {
2.
constructor(props) {
3.
super(props);
4.
this.state = { name: '' };
5.
6.
this.handleChange =
this.handleChange.bind(this);
7.
this.handleSubmit =
this.handleSubmit.bind(this);
8.
}
9.
10.
handleChange(event) {
11.
this.setState({ name:
event.target.value });
12.
}
13.
14.
handleSubmit(event) {
15.
alert('A name was submitted: ' +
this.state.name);
16.
event.preventDefault();
17.
}
18.
19.
render() {
20.
return (
21.
<form onSubmit=
{this.handleSubmit}>
22.
<label>
23.
Name:
24.
<input type="text" value=
{this.state.name} onChange=
{this.handleChange} />
25.
</label>
26.
<button
type="submit">Submit</button>
27.
</form>
28.
);
29.
}
30.
}
1.
class ContactForm extends
React.Component {
2.
// ... constructor and handleChange
method
3.
4.
handleSubmit(event) {
5.
if (!this.state.name) {
6.
alert('Name is required');
7.
event.preventDefault();
8.
return;
9.
}
10.
// Further processing here
11.
}
12.
13.
// ... render method
14.
}
1.
class EmailForm extends
React.Component {
2.
// ... constructor and other methods
3.
4.
validateEmail(email) {
5.
const re = /^[a-zA-Z0-9._-]+@[a-zA-
Z0-9.-]+\.[a-zA-Z]{2,4}$/;
6.
return re.test(email);
7.
}
8.
9.
handleSubmit(event) {
10.
if (!this.validateEmail(this.state.email))
{
11.
alert('Please enter a valid email
address');
12.
event.preventDefault();
13.
return;
14.
}
15.
// Further processing here
16.
}
17.
18.
// ... render method
19.
}
Real-Time Validation
Real-time validation provides instant feedback as the user
fills out the form, improving the user experience.
Example of Real-Time Validation:
1.
class ContactForm extends
React.Component {
2.
// ... constructor
3.
4.
handleChange(event) {
5.
const { name, value } =
event.target;
6.
this.setState({ [name]: value }, ()
=> {
7.
if (name === 'email') {
8.
this.validateEmail();
9.
}
10.
});
11.
}
12.
13.
validateEmail() {
14.
if (!this.validateEmail(this.state.email))
{
15.
this.setState({ emailError: 'Invalid
email' });
16.
} else {
17.
this.setState({ emailError: '' });
18.
}
19.
}
20.
21.
// ... render method including displaying
error messages
22.
}
Best Practices
1.
class ControlledForm extends
React.Component {
2.
constructor(props) {
3.
super(props);
4.
this.state = { value: '' };
5.
6.
this.handleChange =
this.handleChange.bind(this);
7.
this.handleSubmit =
this.handleSubmit.bind(this);
8.
}
9.
10.
handleChange(event) {
11.
this.setState({value:
event.target.value});
12.
}
13.
14.
handleSubmit(event) {
15.
alert('A name was submitted: ' +
this.state.value);
16.
event.preventDefault();
17.
}
18.
19.
render() {
20.
return (
21.
<form onSubmit=
{this.handleSubmit}>
22.
<label>
23.
Name:
24.
<input type="text" value=
{this.state.value} onChange=
{this.handleChange} />
25.
</label>
26.
<button
type="submit">Submit</button>
27.
</form>
28.
);
29.
}
30.
}
1.
class UncontrolledForm extends
React.Component {
2.
constructor(props) {
3.
super(props);
4.
this.input = React.createRef();
5.
}
6.
7.
handleSubmit = event => {
8.
alert('A name was submitted: ' +
this.input.current.value);
9.
event.preventDefault();
10.
}
11.
12.
render() {
13.
return (
14.
<form onSubmit=
{this.handleSubmit}>
15.
<label>
16.
Name:
17.
<input type="text" ref={this.input}
/>
18.
</label>
19.
<button
type="submit">Submit</button>
20.
</form>
21.
);
22.
}
23.
}
Basics of Routing
Routing is a critical aspect of any web application, and in
React.js, this is commonly managed through a popular
library called React Router. React Router provides a robust
and flexible solution to manage navigation and rendering of
different components based on the URL.
React Router is a standard library for routing in React. It
enables the navigation among views of various components
in a React Application, allows changing the browser URL,
and keeps the UI in sync with the URL.
Key Features of React Router
1.
import React from 'react';
2.
import { BrowserRouter as Router,
Route, Link } from 'react-router-dom';
3.
4.
function Home() {
5.
return <h2>Home</h2>;
6.
}
7.
8.
function About() {
9.
return <h2>About</h2>;
10.
}
11.
12.
function App() {
13.
return (
14.
<Router>
15.
<div>
16.
<nav>
17.
<ul>
18.
<li>
19.
<Link to="/">Home</Link>
20.
</li>
21.
<li>
22.
<Link to="/about">About</Link>
23.
</li>
24.
</ul>
25.
</nav>
26.
27.
{/* A <Route> looks through its
children <Route>s and
28.
renders the first one that matches
the current URL. */}
29.
<Route path="/" exact component=
{Home} />
30.
<Route path="/about" component=
{About} />
31.
</div>
32.
</Router>
33.
);
34.
}
35.
36.
export default App;
1.
<Route path="/users/:id" component=
{User} />
1.
function App() {
2.
return (
3.
<Router>
4.
<Route path="/" component=
{Layout} />
5.
</Router>
6.
);
7.
}
8.
9.
function Layout() {
10.
return (
11.
<div>
12.
<Route exact path="/" component=
{Home} />
13.
<Route path="/about" component=
{About} />
14.
</div>
15.
);
16.
}
1.
import { BrowserRouter as Router,
Route, Link } from 'react-router-dom';
2.
3.
function App() {
4.
return (
5.
<Router>
6.
<div>
7.
<Link to="/user/john">John's
Profile</Link>
8.
9.
<Route path="/user/:username"
component={UserProfile} />
10.
</div>
11.
</Router>
12.
);
13.
}
14.
15.
function UserProfile({ match }) {
16.
return <h2>User:
{match.params.username}</h2>;
17.
}
1.
function UserProfile({ match }) {
2.
const { username } = match.params;
3.
// You can use `username` to fetch
user data or perform other logic
4.
return <h2>User: {username}
</h2>;
5.
}
4.
5.
function Product({ match }) {
6.
return (
7.
<div>
8.
<h2>Product: {match.params.productId}</h2>
9.
<Route path={`${match.path}/review/:reviewId`} component=
{Review} />
10.
</div>
11.
);
12.
}
13.
14.
function Review({ match }) {
15.
return <h3>Review: {match.params.reviewId}</h3>;
16.
}
1.
import { useParams } from 'react-
router-dom';
2.
3.
function UserProfile() {
4.
const { username } = useParams();
5.
return <h2>User: {username}
</h2>;
6.
}
1.
import { BrowserRouter as Router,
Route, Link } from 'react-router-dom';
2.
3.
function App() {
4.
return (
5.
<Router>
6.
<Route path="/" component=
{MainLayout}>
7.
<Route path="dashboard"
component={Dashboard} />
8.
<Route path="settings"
component={Settings} />
9.
</Route>
10.
</Router>
11.
);
12.
}
13.
14.
function MainLayout({ children }) {
15.
return (
16.
<div>
17.
<header>Header Content</header>
18.
<main>{children}</main>
19.
<footer>Footer Content</footer>
20.
</div>
21.
);
22.
}
23.
24.
function Dashboard() {
25.
return <div>Dashboard View</div>;
26.
}
27.
28.
function Settings() {
29.
return <div>Settings View</div>;
30.
}
In this setup, MainLayout serves as the primary layout, with
Dashboard and Settings as nested routes.
Creating Layout Components
Layout components in React are used to define a common
structure for pages or groups of pages in your application. A
layout typically includes elements like headers, footers,
navigation menus, and sidebars that are common across
multiple pages.
Example of a Layout Component:
1.
function BasicLayout({ children }) {
2.
return (
3.
<div>
4.
<header>App Header</header>
5.
<main>{children}</main>
6.
<footer>App Footer</footer>
7.
</div>
8.
);
9.
}
1.
function App() {
2.
return (
3.
<Router>
4.
<BasicLayout>
5.
<Route exact path="/"
component={Home} />
6.
<Route path="/about"
component={About} />
7.
<Route path="/contact"
component={Contact} />
8.
</BasicLayout>
9.
</Router>
10.
);
11.
}
12.
13.
function Home() {
14.
return <div>Home Page</div>;
15.
}
16.
17.
function About() {
18.
return <div>About Page</div>;
19.
}
20.
21.
function Contact() {
22.
return <div>Contact Page</div>;
23.
}
1.
const addTodoAction = {
2.
type: 'todos/todoAdded',
3.
payload: 'Buy milk'
4.
}
1.
function todosReducer(state = [],
action) {
2.
switch (action.type) {
3.
case 'todos/todoAdded':
4.
return [...state, action.payload];
5.
default:
6.
return state;
7.
}
8.
}
Setting Up Redux in a React Application
To use Redux in a React application, you typically set up a
Redux store and use the React-Redux library to connect
React components to the Redux store.
Installing Redux and React-Redux:
npm install redux react-redux
Configuring the Store:
1.
import { createStore } from 'redux';
2.
import rootReducer from './reducers';
3.
4.
const store =
createStore(rootReducer);
1.
import React from 'react';
2.
import { Provider } from 'react-redux';
3.
import { store } from './store';
4.
import App from './App';
5.
6.
export default function Root() {
7.
return (
8.
<Provider store={store}>
9.
<App />
10.
</Provider>
11.
);
12.
}
1.
import { createStore, applyMiddleware
} from 'redux';
2.
import thunk from 'redux-thunk';
3.
import rootReducer from './reducers';
4.
5.
const store = createStore(
6.
rootReducer,
7.
applyMiddleware(thunk)
8.
);
9.
10.
export default store;
1.
function fetchUserData(userId) {
2.
return function(dispatch, getState) {
3.
dispatch({ type: 'LOADING_USER_DATA' });
4.
5.
fetch(`/api/user/${userId}`)
6.
.then(response => response.json())
7.
.then(data => dispatch({ type: 'USER_DATA_FETCHED', payload:
data }))
8.
.catch(error => dispatch({ type:
'ERROR_FETCHING_USER_DATA', error }));
9.
};
10.
}
1.
import React, { useEffect } from 'react';
2.
import { useDispatch } from 'react-
redux';
3.
import { fetchUserData } from
'./actions/userActions';
4.
5.
function UserProfile({ userId }) {
6.
const dispatch = useDispatch();
7.
8.
useEffect(() => {
9.
dispatch(fetchUserData(userId));
10.
}, [dispatch, userId]);
11.
12.
// Render user profile...
13.
}
1.
function fetchUserData(userId) {
2.
return function(dispatch) {
3.
dispatch({ type:
'LOADING_USER_DATA' });
4.
5.
fetch(`/api/user/${userId}`)
6.
.then(response => response.json())
7.
.then(data => dispatch({ type:
'USER_DATA_FETCHED', payload: data }))
8.
.catch(error => {
9.
console.error('Fetch error:', error);
10.
dispatch({ type:
'ERROR_FETCHING_USER_DATA', error });
11.
});
12.
};
13.
}
Best Practices
1.
import React from 'react';
2.
import { render, screen } from
'@testing-library/react';
3.
import Greeting from './Greeting';
4.
5.
test('renders a greeting message', ()
=> {
6.
render(<Greeting name="World" />);
7.
expect(screen.getByText('Hello,
World!')).toBeInTheDocument();
8.
});
1.
import React from 'react';
2.
import { render, fireEvent, screen } from '@testing-library/react';
3.
import App from './App';
4.
5.
test('submits form data', () => {
6.
render(<App />);
7.
fireEvent.change(screen.getByLabelText(/name/i), { target: {
value: 'John Doe' } });
8.
fireEvent.click(screen.getByText(/submit/i));
9.
expect(screen.getByText(/submitted/i)).toBeInTheDocument();
10.
});
1.
describe('The Home Page', () => {
2.
it('successfully loads', () => {
3.
cy.visit('/');
4.
cy.contains('Welcome to React');
5.
cy.get('input').type('React
Testing{enter}');
6.
cy.url().should('include', '/search');
7.
});
8.
});
In this Cypress test, we are visiting the home page,
performing actions like typing in a search box, and asserting
that the application behaves as expected.
Best Practices in Testing React Components
1.
module.exports = {
2.
setupFilesAfterEnv:
['<rootDir>/src/setupTests.js'],
3.
};
1.
// Greeting.js
2.
import React from 'react';
3.
4.
const Greeting = ({ name }) =>
<h1>Hello, {name}!</h1>;
5.
6.
export default Greeting;
1.
// Greeting.test.js
2.
import React from 'react';
3.
import { render } from '@testing-
library/react';
4.
import Greeting from './Greeting';
5.
6.
test('renders a greeting message', ()
=> {
7.
const { getByText } =
render(<Greeting name="World" />);
8.
expect(getByText('Hello,
World!')).toBeInTheDocument();
9.
});
1.
// CounterButton.js
2.
import React, { useState } from 'react';
3.
4.
const CounterButton = () => {
5.
const [count, setCount] = useState(0);
6.
7.
return (
8.
<button onClick={() =>
setCount(count + 1)}>
9.
Clicked {count} times
10.
</button>
11.
);
12.
};
13.
14.
export default CounterButton;
1.
// CounterButton.test.js
2.
import React from 'react';
3.
import { render, fireEvent } from
'@testing-library/react';
4.
import CounterButton from
'./CounterButton';
5.
6.
test('increments counter on button
click', () => {
7.
const { getByText } =
render(<CounterButton />);
8.
const button = getByText(/clicked 0
times/i);
9.
10.
fireEvent.click(button);
11.
expect(button).toHaveTextContent('Click
ed 1 times');
12.
});
Best Practices
1.
// Button.js
2.
import React from 'react';
3.
4.
const Button = ({ label, onClick }) => (
5.
<button onClick={onClick}>{label}
</button>
6.
);
7.
8.
export default Button;
A unit test for this component using Jest and React Testing
Library might look like this:
1.
// Button.test.js
2.
import React from 'react';
3.
import { render, fireEvent } from
'@testing-library/react';
4.
import Button from './Button';
5.
6.
test('renders the button with a label', ()
=> {
7.
const label = 'Click me';
8.
const { getByText } = render(<Button
label={label} onClick={() => {}} />);
9.
expect(getByText(label)).toBeInTheDo
cument();
10.
});
11.
12.
test('calls onClick when clicked', () => {
13.
const handleClick = jest.fn();
14.
const { getByText } = render(<Button
label="Click me" onClick={handleClick} />);
15.
fireEvent.click(getByText('Click me'));
16.
expect(handleClick).toHaveBeenCalled();
17.
});
These tests verify that the Button renders with the correct
label and that the onClick function is called when the button
is clicked.
Integration Testing
Integration testing in React involves testing the interaction
between multiple components. It’s about ensuring that
components work correctly together as a unit.
Example of an Integration Test:
Consider a LoginForm component that contains several
Input components and a SubmitButton component.
1.
// LoginForm.test.js
2.
import React from 'react';
3.
import { render, fireEvent } from
'@testing-library/react';
4.
import LoginForm from './LoginForm';
5.
6.
test('submits form with username and
password', () => {
7.
const handleLogin = jest.fn();
8.
const { getByLabelText, getByText } =
render(<LoginForm onLogin={handleLogin} />);
9.
10.
fireEvent.change(getByLabelText(/userna
me/i), { target: { value: 'user1' } });
11.
fireEvent.change(getByLabelText(/passw
ord/i), { target: { value: 'password' } });
12.
fireEvent.click(getByText(/submit/i));
13.
14.
expect(handleLogin).toHaveBeenCalledW
ith({ username: 'user1', password: 'password' });
15.
});
1.
// UserFetcher.test.js
2.
import React from 'react';
3.
import { render, waitFor } from
'@testing-library/react';
4.
import UserFetcher from
'./UserFetcher';
5.
6.
test('fetches and displays user', async
() => {
7.
jest.spyOn(global,
'fetch').mockResolvedValue({
8.
json: jest.fn().mockResolvedValue({
name: 'John Doe' })
9.
});
10.
11.
const { getByText } =
render(<UserFetcher userId="123" />);
12.
await waitFor(() =>
expect(getByText(/John
Doe/i)).toBeInTheDocument());
13.
14.
global.fetch.mockRestore();
15.
});
6.
7.
jest.mock('./api');
8.
9.
test('displays user data', async () => {
10.
api.fetchUser.mockResolvedValue({ name: 'John Doe'
});
11.
12.
const { getByText } = render(<User userId="123" />);
13.
await waitFor(() => expect(getByText(/John
Doe/)).toBeInTheDocument());
14.
});
1.
// User.js
2.
import React, { useEffect, useState }
from 'react';
3.
import { fetchUser } from './api';
4.
5.
const User = ({ userId }) => {
6.
const [user, setUser] =
useState(null);
7.
8.
useEffect(() => {
9.
fetchUser(userId).then(data =>
setUser(data));
10.
}, [userId]);
11.
12.
if (!user) return <div>Loading...</div>;
13.
return <div>{user.name}</div>;
14.
};
6.
7.
jest.mock('./api');
8.
9.
test('displays user data', async () => {
10.
api.fetchUser.mockResolvedValue({ name: 'John Doe'
});
11.
12.
const { getByText } = render(<User userId="123" />);
13.
await waitFor(() => expect(getByText(/John
Doe/)).toBeInTheDocument());
14.
});
Here, waitFor is used to wait for the User component to
update its state with the fetched data and re-render.
Best Practices for Mocking and Asynchronous Testing
1.
const MyComponent = React.memo(({
value }) => {
2.
// Component implementation
3.
return <div>{value}</div>;
4.
});
1.
const MyComponent = React.memo(
2.
({ value }) => {
3.
// Component implementation
4.
return <div>{value}</div>;
5.
},
6.
(prevProps, nextProps) => {
7.
// Custom comparison logic
8.
return prevProps.value ===
nextProps.value;
9.
}
10.
);
1.
class MyComponent extends
React.PureComponent {
2.
render() {
3.
return <div>{this.props.value}
</div>;
4.
}
5.
}
MyComponent extends PureComponent and will only re-
render if its props or state change.
Techniques for Optimizing Re-renders
1. Avoiding Inline Functions and Objects in JSX: Inline
functions and objects are created new on each render,
triggering re-renders in child components.
1.
// Avoid this
2.
<MyComponent onClick={() =>
console.log('clicked')} />
3.
4.
// Prefer this
5.
<MyComponent onClick=
{this.handleClick} />
1.
const memoizedCallback =
useCallback(() => {
2.
doSomething(a, b);
3.
}, [a, b]);
4.
5.
const memoizedValue = useMemo(()
=> computeExpensiveValue(a, b), [a, b]);
1.
class MyComponent extends
React.Component {
2.
shouldComponentUpdate(nextProps,
nextState) {
3.
// Custom comparison logic
4.
}
5.
// ...
6.
}
3.
4.
// Use dynamic import
5.
const MyComponent = React.lazy(() => import('./MyComponent'));
Example of React.lazy:
1.
import React, { Suspense } from
'react';
2.
3.
const MyComponent = React.lazy(()
=> import('./MyComponent'));
4.
5.
function App() {
6.
return (
7.
<Suspense fallback=
{<div>Loading...</div>}>
8.
<MyComponent />
9.
</Suspense>
10.
);
11.
}
3.
4.
const Home = React.lazy(() => import('./Home'));
5.
const About = React.lazy(() => import('./About'));
6.
7.
function App() {
8.
return (
9.
<Router>
10.
<Suspense fallback={<div>Loading...</div>}>
11.
<Switch>
12.
<Route exact path="/" component={Home} />
13.
<Route path="/about" component={About} />
14.
</Switch>
15.
</Suspense>
16.
</Router>
17.
);
18.
}
1.
class ErrorBoundary extends
React.Component {
2.
// Error boundary implementation
3.
}
4.
5.
const LazyComponent = React.lazy(()
=> import('./SomeComponent'));
6.
7.
function App() {
8.
return (
9.
<ErrorBoundary>
10.
<Suspense fallback={<div>Loading...
</div>}>
11.
<LazyComponent />
12.
</Suspense>
13.
</ErrorBoundary>
14.
);
15.
}
1.
npx create-react-app react-blog-app
2.
cd react-blog-app
1.
react-blog-app/
2.
├── public/
3.
├── src/
4.
│ ├── components/
5.
│ │ ├── Header.js
6.
│ │ ├── Footer.js
7.
│ │ ├── PostList.js
8.
│ │ ├── Post.js
9.
│ │ └── CommentSection.js
10.
│ ├── App.js
11.
│ └── index.js
12.
└── package.json
1.
// Header.js
2.
const Header = () => <header>React
Blog</header>;
3.
4.
// Footer.js
5.
const Footer = () => <footer>© 2023
React Blog</footer>;
1.
// PostList.js
2.
import React from 'react';
3.
import Post from './Post';
4.
5.
const PostList = ({ posts }) => (
6.
<div>
7.
{posts.map(post => <Post key=
{post.id} post={post} />)}
8.
</div>
9.
);
10.
11.
// Post.js
12.
const Post = ({ post }) => (
13.
<article>
14.
<h2>{post.title}</h2>
15.
<p>{post.content}</p>
16.
</article>
17.
);
3. CommentSection Component: Allow users to read and
add comments to posts.
1.
// CommentSection.js
2.
import React, { useState } from 'react';
3.
4.
const CommentSection = ({ postId })
=> {
5.
const [comments, setComments] =
useState([]);
6.
// Comment logic will be added here
7.
return (
8.
<section>
9.
<h3>Comments</h3>
10.
{/* Comment list */}
11.
</section>
12.
);
13.
};
1.
// In PostList.js
2.
import React, { useState, useEffect }
from 'react';
3.
4.
const PostList = () => {
5.
const [posts, setPosts] = useState([]);
6.
7.
useEffect(() => {
8.
fetch('https://api.myblog.com/posts')
9.
.then(response => response.json())
10.
.then(data => setPosts(data));
11.
}, []);
12.
13.
return (/* Render posts */);
14.
};
Step 4: Connecting to a Backend API
1. Fetching Data: Fetch posts and comments from a
backend API (for this example, we'll use a mock API).
1.
// Example of fetching posts
2.
useEffect(() => {
3.
fetch('https://api.myblog.com/posts')
4.
.then(response => response.json())
5.
.then(data => setPosts(data));
6.
}, []);
1.
// In CommentSection.js
2.
const addComment = (commentText)
=> {
3.
fetch(`https://api.myblog.com/posts/$
{postId}/comments`, {
4.
method: 'POST',
5.
body: JSON.stringify({ text:
commentText }),
6.
headers: {
7.
'Content-Type': 'application/json'
8.
}
9.
})
10.
.then(response => response.json())
11.
.then(newComment =>
setComments([...comments, newComment]));
12.
};
1.
npm install react-router-dom
1.
// In App.js
2.
import { BrowserRouter as Router,
Route, Switch } from 'react-router-dom';
3.
import PostList from
'./components/PostList';
4.
import Post from './components/Post';
5.
6.
const App = () => (
7.
<Router>
8.
<Header />
9.
<Switch>
10.
<Route exact path="/" component=
{PostList} />
11.
<Route path="/post/:id" component=
{Post} />
12.
</Switch>
13.
<Footer />
14.
</Router>
15.
);
1.
/* In App.css */
2.
header {
3.
/* Header styles */
4.
}
5.
article {
6.
/* Post styles */
7.
}
8.
footer {
9.
/* Footer styles */
10.
}
1.
npm install redux react-redux
1.
// Example test for Post component
2.
import { render } from '@testing-
library/react';
3.
import Post from './components/Post';
4.
5.
test('renders post content', () => {
6.
const { getByText } = render(<Post
title="Test Post" content="This is a test" />);
7.
expect(getByText('This is a
test')).toBeInTheDocument();
8.
});
Step 9: Deployment
1. Build the Application: Create a production build of your
application.
1.
npm run build
1.
import React, { useState, useEffect }
from 'react';
2.
3.
function PostList() {
4.
const [posts, setPosts] = useState([]);
5.
6.
useEffect(() => {
7.
fetch('https://api.myapp.com/posts')
8.
.then(response => response.json())
9.
.then(data => setPosts(data))
10.
.catch(error => console.error('Error
fetching posts:', error));
11.
}, []);
12.
13.
return (
14.
<ul>
15.
{posts.map(post => (
16.
<li key={post.id}>{post.title}</li>
17.
))}
18.
</ul>
19.
);
20.
}
1.
npm install axios
1.
import React, { useState, useEffect }
from 'react';
2.
import axios from 'axios';
3.
4.
function PostList() {
5.
const [posts, setPosts] = useState([]);
6.
7.
useEffect(() => {
8.
axios.get('https://api.myapp.com/pos
ts')
9.
.then(response =>
setPosts(response.data))
10.
.catch(error => console.error('Error
fetching posts:', error));
11.
}, []);
12.
13.
return (
14.
// Render posts
15.
);
16.
}
1.
const [loading, setLoading] =
useState(false);
2.
3.
useEffect(() => {
4.
setLoading(true);
5.
fetch('https://api.myapp.com/posts')
6.
.then(response => response.json())
7.
.then(data => {
8.
setPosts(data);
9.
setLoading(false);
10.
});
11.
}, []);
12.
13.
if (loading) return <div>Loading...
</div>;
1.
const [error, setError] = useState(null);
2.
3.
useEffect(() => {
4.
fetch('https://api.myapp.com/posts')
5.
.then(response => response.json())
6.
.then(data => setPosts(data))
7.
.catch(error => {
8.
console.error('Error fetching posts:',
error);
9.
setError(error);
10.
});
11.
}, []);
12.
13.
if (error) return <div>Error loading
posts</div>;
1.
npm run build