ReactJs
Fundamentals

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.

  1. Component-based architecture: React allows you to create reusable components, which make the UI easier to manage and maintain.
  2. Virtual DOM: React optimizes rendering by using a virtual DOM, which updates only the changed parts of the real DOM, improving performance.
  3. Declarative syntax: Instead of directly manipulating the DOM, you define what the UI should look like, and React automatically updates it when data changes.
  4. Unidirectional data flow: Data flows in one direction (from parent to child), making apps predictable and easier to debug.
  5. 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

  1. 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.

  2. 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.
  3. 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.

  4. 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

  1. JSX lets you write UI code that looks like HTML.
  2. Babel converts JSX into plain JavaScript that React can understand.
  3. 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 and age to Child.
  • 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 at 0 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

FeaturePropsState
MutabilityImmutable (read-only)Mutable (can change within the component)
OwnershipManaged by parent and passed downManaged within the component
Re-renderChanges do not trigger a re-renderChanges trigger a re-render
Use CasePassing external dataHandling 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 from Parent to Counter.
  • State (count) in Counter is initialized with the initialCount 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 and componentDidUpdate.
  • 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 using this.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

FeatureFunctional ComponentsClass-based Components
SyntaxFunction-based, lightweightES6 Class-based, more verbose
State ManagementUses Hooks (useState, useReducer)Managed using this.state
Lifecycle MethodsSimulated with Hooks (useEffect)Explicit lifecycle methods (componentDidMount, etc.)
PerformanceTypically faster due to simpler structureSlower due to the complexity of class instantiation
ReadabilityEasier to read and writeMore boilerplate and complex to follow
Recommended for New AppsYes, due to simplicity and flexibilityNo, 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 StageClass-based ComponentFunctional Component (Hooks)
Mounting (Initial render)componentDidMountuseEffect(() => {}, [])
Updating (Prop/State change)componentDidUpdateuseEffect(() => {}, [dependency])
Unmounting (Cleanup)componentWillUnmountuseEffect(() => { 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 requires componentDidMount, componentWillUnmount, and explicit binding of this.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.