ReactJs Fundamentals
What is ReactJs
React is a JavaScript library developed by Facebook for building user interfaces (UIs), specifically focused on creating reusable UI components.
- Component-based architecture: React allows you to create reusable components, which make the UI easier to manage and maintain.
- Virtual DOM: React optimizes rendering by using a virtual DOM, which updates only the changed parts of the real DOM, improving performance.
- Declarative syntax: Instead of directly manipulating the DOM, you define what the UI should look like, and React automatically updates it when data changes.
- Unidirectional data flow: Data flows in one direction (from parent to child), making apps predictable and easier to debug.
- JSX: A syntax extension that allows HTML-like code within JavaScript, improving code readability.
Example:
import React from 'react';
import ReactDOM from 'react-dom';
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
ReactDOM.render(<Welcome name="World" />, document.getElementById('root'));
React is efficient, modular, and ideal for building dynamic UIs.
Summary
- Advantages: Component-based structure, efficient updates with virtual DOM, ease of learning, and a strong ecosystem.
- Disadvantages: Rapid changes, JSX learning curve, state management complexity, and SEO limitations with client-side rendering.
Understanding JSX
JSX stands for JavaScript XML. It’s a syntax extension for JavaScript used with React. It allows you to write HTML-like code within your JavaScript files. This makes it easier to create and manage React components.
How JSX Works
-
Write JSX Code
Here’s an example of JSX code:
const element = <h1>Hello, world!</h1>;
This looks like HTML but is actually JavaScript code.
-
Convert JSX to JavaScript
Browsers don’t understand JSX directly. So, tools like Babel convert JSX into plain JavaScript.
JSX:
const element = <h1>Hello, world!</h1>;
JavaScript:
const element = React.createElement('h1', null, 'Hello, world!');
React.createElement
: This function creates a React element.'h1'
: The type of element to create.null
: Properties or attributes (none in this case).'Hello, world!'
: The content of the element.
-
Virtual DOM
React uses the virtual DOM (an in-memory representation of the real DOM) to efficiently update the user interface. When changes occur, React compares the virtual DOM with the real DOM and updates only what’s necessary.
-
Rendering
The transformed JavaScript code is executed by React to render the UI. React handles the actual changes to the real DOM based on the virtual DOM.
Example in Action
JSX Code:
function MyComponent() {
return <div className="container">Hello, React!</div>;
}
Converted JavaScript:
function MyComponent() {
return React.createElement(
'div',
{ className: 'container' },
'Hello, React!'
);
}
Summary
- JSX lets you write UI code that looks like HTML.
- Babel converts JSX into plain JavaScript that React can understand.
- React uses this JavaScript to create and update the user interface efficiently.
JSX makes writing React components simpler and more intuitive by combining HTML-like syntax with JavaScript logic.
Understanding Virtual DOM
The Virtual DOM (VDOM) is a fundamental concept in React that significantly optimizes rendering performance. It allows React to update the user interface efficiently by minimizing the number of operations on the actual DOM.
What is the Virtual DOM?
The Virtual DOM is a lightweight, in-memory representation of the real DOM. It acts as an intermediary between React components and the real DOM, allowing React to perform updates in a more controlled and efficient manner.
Detailed Workflow
1. Initial Render
When a React component is first rendered, React constructs a Virtual DOM tree that mirrors the structure of the real DOM.
Example:
JSX:
const element = <h1>Hello, world!</h1>;
Virtual DOM Representation:
{
type: 'h1',
props: {
children: 'Hello, world!'
}
}
- Virtual DOM Tree Creation: React creates this virtual representation to reflect the initial state of the UI.
2. Component Updates
When a component’s state or props change, React generates a new Virtual DOM tree. This new tree represents the updated state of the UI.
Example:
JSX:
function MyComponent({ message }) {
return <h1>{message}</h1>;
}
If message
changes from 'Hello'
to 'Hello, React!'
, React creates a new Virtual DOM tree:
New Virtual DOM Representation:
{
type: 'h1',
props: {
children: 'Hello, React!'
}
}
3. Diffing Algorithm
React employs a sophisticated diffing algorithm to compare the previous and new Virtual DOM trees. This process identifies changes (deltas) efficiently.
- Algorithm Steps:
- Tree Traversal: React traverses both Virtual DOM trees to compare nodes.
- Element Type Comparison: If the element type changes, React replaces the entire subtree.
- Props Comparison: If the element type remains the same, React compares the properties. It updates only the properties that have changed.
Example Diffing:
- Old Virtual DOM:
{ type: 'h1', props: { children: 'Hello' } }
- New Virtual DOM:
{ type: 'h1', props: { children: 'Hello, React!' } }
React identifies that only the text content of the <h1>
tag has changed and updates this specific part of the real DOM.
4. Reconciliation
After determining the differences, React performs the actual updates on the real DOM using a process known as reconciliation.
- Batching Updates: React may batch multiple updates to reduce the number of DOM operations and improve performance.
- Efficient DOM Manipulations: React applies updates in a way that minimizes reflows and repaints, ensuring a smooth user experience.
- Asynchronous Rendering: React can defer updates to avoid blocking the main thread, keeping the UI responsive.
Advanced Considerations
- Key Prop: When rendering lists, React uses the
key
prop to identify elements and optimize updates. Keys help React match old elements with new ones, ensuring that only necessary changes are made. - Reconciliation Strategies: React uses heuristics like "two-way reconciliation" to handle different types of changes, such as insertion, deletion, and updates, effectively.
Benefits of the Virtual DOM
- Performance Optimization: By updating only the parts of the DOM that have changed, React reduces unnecessary operations, leading to faster rendering times.
- Efficient Rendering: Minimizes the cost of direct DOM manipulation, which is typically slow compared to in-memory operations.
- Predictable UI Updates: Ensures that the UI remains consistent by applying changes in a controlled and predictable manner.
Summary
- Virtual DOM: An in-memory representation of the real DOM used to optimize updates.
- Initial Render: Creates a Virtual DOM tree from JSX.
- Updates: Generates a new Virtual DOM tree on state/props changes.
- Diffing Algorithm: Compares old and new VDOM trees to identify and apply changes.
- Reconciliation: Efficiently updates the real DOM with minimal operations.
The Virtual DOM is a key factor in React’s performance optimization, allowing it to manage updates efficiently and provide a smooth user experience.
Props vs State in React
In React, both props and state are fundamental for managing data, but they have distinct roles.
Props
Props (short for properties) are used to pass data from a parent component to a child component. Props are read-only, meaning they cannot be modified by the child component. This makes them ideal for sending data to a component without allowing that component to alter it.
Key Characteristics:
- Immutable: Once set, cannot be changed by the receiving component.
- Unidirectional Data Flow: Props flow from parent to child.
- Stateless: Props are external and do not involve state management.
Example:
// Parent Component
function Parent() {
return <Child name="Alice" age={25} />;
}
// Child Component
function Child(props) {
return <div>{props.name} is {props.age} years old.</div>;
}
- Parent passes
name
andage
toChild
. - Child uses props to display the data without modifying it.
State
State is used to manage data that is internal to a component. Unlike props, state is mutable, meaning the component can change its state over time, typically as a response to user interaction or other events. Changing state triggers a re-render of the component.
Key Characteristics:
- Mutable: Can be updated by the component itself.
- Local to the Component: Managed by the component, not passed down like props.
- Triggers Re-renders: When state changes, React re-renders the component.
Example:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
- State Variable (
count
): Initialized at0
and updated on button click. - setCount() updates the state, causing React to re-render the component with the new count.
Key Differences: Props vs State
Feature | Props | State |
---|---|---|
Mutability | Immutable (read-only) | Mutable (can change within the component) |
Ownership | Managed by parent and passed down | Managed within the component |
Re-render | Changes do not trigger a re-render | Changes trigger a re-render |
Use Case | Passing external data | Handling dynamic, internal data |
Combining Props and State
Often, props and state are used together to pass initial values and then manage the component's internal state.
function Parent() {
return <Counter initialCount={5} />;
}
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
- Props (
initialCount
) are passed fromParent
toCounter
. - State (
count
) inCounter
is initialized with theinitialCount
and can be updated.
Summary
- Props are immutable and passed from parent to child.
- State is mutable and managed internally by a component.
- Props + State can be combined for initial values and dynamic updates, enabling React to efficiently handle UI changes.
React Components
In React, components are the building blocks of the user interface. They can be written as either functional components or class-based components. Both types of components allow you to manage state and handle lifecycle events, but they do so in different ways.
1. Functional Components
Functional components are JavaScript functions that return JSX (HTML-like syntax). Before React 16.8, functional components were stateless, but with the introduction of React Hooks, they can now manage state, side effects, and lifecycle events.
Key Features:
- Simpler and more readable syntax.
- Use React Hooks (
useState
,useEffect
, etc.) for state management and lifecycle-like behavior. - Easier to test and debug.
- Encouraged for new development due to better performance and simplicity.
Example: Functional Component with Hooks
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null); // state for user data
const [loading, setLoading] = useState(true); // state for loading status
// useEffect to simulate componentDidMount and componentDidUpdate
useEffect(() => {
async function fetchUser() {
setLoading(true);
const response = await fetch(`https://api.example.com/user/${userId}`);
const data = await response.json();
setUser(data); // update state with user data
setLoading(false); // stop loading once data is fetched
}
fetchUser(); // fetch user data when component mounts or userId changes
}, [userId]); // dependency array ensures the effect runs when userId changes
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
export default UserProfile;
Explanation:
- useState: Manages state in the functional component (
user
,loading
). - useEffect: Handles side effects, such as data fetching, simulating lifecycle methods like
componentDidMount
andcomponentDidUpdate
. - The component fetches and displays user data based on the
userId
prop.
2. Class-based Components
Class-based components were the primary way to manage state and lifecycle methods before the introduction of Hooks. They rely on ES6 classes and provide access to more explicit lifecycle methods such as componentDidMount
, componentDidUpdate
, and componentWillUnmount
.
Key Features:
- More verbose syntax due to the class structure.
- State is managed through
this.state
and updated usingthis.setState
. - Lifecycle methods explicitly define various stages of the component’s life.
- Still used in many legacy codebases but less recommended for new projects.
Example: Class-based Component with Lifecycle Methods
import React, { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
// Initialize state
this.state = {
user: null,
loading: true
};
}
// Fetch user data when the component is mounted
componentDidMount() {
this.fetchUserData(this.props.userId);
}
// Fetch user data if userId prop changes
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUserData(this.props.userId);
}
}
// Clean up if necessary when component unmounts
componentWillUnmount() {
// cleanup logic like aborting network requests
}
// Method to fetch user data
async fetchUserData(userId) {
this.setState({ loading: true });
const response = await fetch(`https://api.example.com/user/${userId}`);
const data = await response.json();
this.setState({ user: data, loading: false });
}
render() {
const { user, loading } = this.state;
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
}
export default UserProfile;
Explanation:
- Constructor: Initializes the state using
this.state
. - componentDidMount: Fetches data when the component mounts.
- componentDidUpdate: Checks if the
userId
prop has changed to refetch user data. - componentWillUnmount: Can be used for cleanup (e.g., aborting network requests).
- this.setState: Updates the component's state when data is fetched or when the user interacts.
Key Differences
Feature | Functional Components | Class-based Components |
---|---|---|
Syntax | Function-based, lightweight | ES6 Class-based, more verbose |
State Management | Uses Hooks (useState , useReducer ) | Managed using this.state |
Lifecycle Methods | Simulated with Hooks (useEffect ) | Explicit lifecycle methods (componentDidMount , etc.) |
Performance | Typically faster due to simpler structure | Slower due to the complexity of class instantiation |
Readability | Easier to read and write | More boilerplate and complex to follow |
Recommended for New Apps | Yes, due to simplicity and flexibility | No, mainly used in legacy projects |
Lifecycle Methods in Class-based Components vs. Hooks in Functional Components
React’s functional components with Hooks provide a cleaner way to handle lifecycle events compared to class-based components. Here’s a comparison:
Lifecycle Stage | Class-based Component | Functional Component (Hooks) |
---|---|---|
Mounting (Initial render) | componentDidMount | useEffect(() => {}, []) |
Updating (Prop/State change) | componentDidUpdate | useEffect(() => {}, [dependency]) |
Unmounting (Cleanup) | componentWillUnmount | useEffect(() => { return cleanup }, []) |
Example: Hook vs Class Lifecycle Equivalent
// Functional Component with useEffect (Lifecycle Simulation)
useEffect(() => {
// Code here acts like componentDidMount and componentDidUpdate
return () => {
// Cleanup code acts like componentWillUnmount
};
}, [dependency]); // dependency array
In functional components, useEffect
can cover all lifecycle stages (mounting, updating, unmounting) in a single function, while class-based components need multiple lifecycle methods for the same.
Complex Example: Switching Between Functional and Class-based Components
Imagine a more complex case where a React component fetches data, listens for window resize events, and performs cleanup on unmount.
Functional Component Version:
import React, { useState, useEffect } from 'react';
function WindowSizeTracker() {
const [windowSize, setWindowSize] = useState(window.innerWidth);
useEffect(() => {
// Event listener for window resize
const handleResize = () => setWindowSize(window.innerWidth);
window.addEventListener('resize', handleResize);
// Cleanup listener on component unmount
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty dependency array ensures this effect runs once on mount and cleanup on unmount
return <h1>Window size: {windowSize}px</h1>;
}
Class-based Component Version:
import React, { Component } from 'react';
class WindowSizeTracker extends Component {
constructor(props) {
super(props);
this.state = { windowSize: window.innerWidth };
this.handleResize = this.handleResize.bind(this);
}
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
componentDidUpdate() {
console.log('Component updated');
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
handleResize() {
this.setState({ windowSize: window.innerWidth });
}
render() {
return <h1>Window size: {this.state.windowSize}px</h1>;
}
}
Explanation:
- Both components track the browser window size and update it dynamically.
- The functional component uses
useEffect
to manage the event listener and perform cleanup, while the class-based component requirescomponentDidMount
,componentWillUnmount
, and explicit binding ofthis.handleResize
.
Conclusion
- Functional Components are now preferred in modern React due to their simpler syntax, better performance, and the power of Hooks to handle state and side effects.
- Class-based Components offer explicit lifecycle management and are still found in legacy applications but are gradually being phased out in favor of functional components.
With Hooks, functional components can achieve everything that class-based components can, but in a more concise, readable, and maintainable way.