Last minute Revision - rs-hash/Learning GitHub Wiki

1. Virtual DOM

light weight in memory representation of actual dom where each node represents component. when the state or component changes, react updates the corresponding node in the virtual dom to reflect the new state. Then it compares with the current version of the virtual dom with previous version to identify the nodes which should be updated. then it will update the difference in the actual dom

The Virtual DOM (VDOM) is a fundamental concept in React, a popular JavaScript library for building user interfaces. It is a key part of React's efficiency and performance optimization strategy.

Here's an explanation of what the Virtual DOM is and how it works in React:

  1. Real DOM vs. Virtual DOM:

    • The Real DOM is the actual document object model of a web page. It represents the structure of the web page, including all HTML elements and their properties.
    • The Virtual DOM, on the other hand, is an abstract, lightweight copy of the Real DOM. It's a JavaScript object that mirrors the structure of the Real DOM.
  2. How the Virtual DOM Works in React:

    • When you create a React component, it renders a Virtual DOM representation of that component's UI.
    • Any changes to the component's state or props trigger a re-render of the Virtual DOM.
    • During this re-render process, React creates a new Virtual DOM tree that reflects the updated state or props.
    • React then compares the new Virtual DOM tree to the previous one (the one from the previous render).
    • It identifies the differences (called "diffing") between the new and old Virtual DOM trees. This process is known as "reconciliation."
  3. Benefits of the Virtual DOM:

    • Performance Optimization: By comparing Virtual DOMs instead of manipulating the Real DOM directly, React can minimize the number of updates to the actual HTML page. This reduces the need for costly and potentially slow operations on the Real DOM.
    • Efficiency: React batches multiple updates and applies them in a single operation, further improving performance.
    • Developer-Friendly: Developers don't have to worry about manually updating the Real DOM; React handles it for them. This simplifies the code and reduces the risk of bugs.
  4. Reconciliation and Updating:

    • After identifying the differences between the new and old Virtual DOM trees, React updates only the parts of the Real DOM that have changed, rather than re-rendering the entire DOM.
    • This selective updating is what makes React highly efficient and fast.

In summary, the Virtual DOM in React acts as an intermediary representation of the UI, allowing React to efficiently update the Real DOM by minimizing the number of changes and updates. This abstraction is a key factor in React's ability to deliver high-performance, interactive user interfaces. Developers can focus on building the UI declaratively, while React takes care of the underlying DOM manipulation.

2. Fragment

In React, a fragment is a way to group multiple elements together without adding an additional DOM element to the rendered output. It's a lightweight wrapper that doesn't create a new parent element in the HTML structure, which can be useful in situations where you need to group elements together, but you don't want to introduce an extra div or any other container element.

Here's how you can define a React fragment:

  1. Using the <React.Fragment> syntax:

  2. Using the shorthand syntax with an empty angle bracket <> and </>:

import React from 'react';

function MyComponent() {
  return (
    <>
      <h1>Hello</h1>
      <p>This is a paragraph.</p>
    </>
  );
}

export default MyComponent;

3.Higher Order component

A Higher-Order Component (HOC) is a design pattern in React that allows you to reuse component logic and behavior. Essentially, it's a function that takes a component and returns a new component with enhanced features or props. HOCs are a way to share common functionality among different components without repeating code.

Here's an example of a Higher-Order Component:

import React, { Component } from 'react';

// A Higher-Order Component that adds a "loading" prop to the wrapped component
const withLoading = (WrappedComponent) => {
  return class WithLoading extends Component {
    render() {
      const { isLoading, ...props } = this.props;
      if (isLoading) {
        return <div>Loading...</div>;
      }
      return <WrappedComponent {...props} />;
    }
  };
};

// Example usage of the HOC
class MyComponent extends Component {
  render() {
    return <div>Data loaded successfully!</div>;
  }
}

const MyComponentWithLoading = withLoading(MyComponent);

// In your application, you can use MyComponentWithLoading
// and pass the "isLoading" prop to control the loading state.

Higher-Order Components are a powerful way to separate concerns in your components and promote code reusability. They can be used for various purposes, such as handling authentication, managing data fetching, adding animation, and more. They are commonly used in React libraries and frameworks to extend and customize component behavior.

5. UseRef

useRef is a React Hook that provides a way to create a mutable reference to a DOM element or to persist values across renders without causing re-renders. Here are some common use cases for useRef in React:

  1. Accessing DOM Elements:

    • One of the primary use cases for useRef is accessing and manipulating DOM elements directly. You can create a ref and attach it to a DOM element, making it accessible in your component.
    import React, { useRef, useEffect } from 'react';
    
    function MyComponent() {
      const myInputRef = useRef(null);
    
      useEffect(() => {
        // Focus on the input element when the component mounts
        myInputRef.current.focus();
      }, []);
    
      return <input ref={myInputRef} />;
    }
  2. Managing Previous Values:

    • useRef can be used to persist values across renders without causing re-renders. Since the current property of a ref does not trigger component updates, you can store and update values without affecting the rendering process.
    import React, { useRef, useEffect } from 'react';
    
    function MyComponent() {
      const prevCountRef = useRef(0);
      const count = 5;
    
      useEffect(() => {
        prevCountRef.current = count;
      }, [count]);
    
      return (
        <div>
          <p>Previous Count: {prevCountRef.current}</p>
          <p>Current Count: {count}</p>
        </div>
      );
    }
  3. Avoiding Stale Closures:

    • When working with asynchronous operations inside closures, you can use useRef to capture the current state of a variable. This prevents the closure from holding a stale (outdated) reference.
    import React, { useState, useEffect, useRef } from 'react';
    
    function MyComponent() {
      const [count, setCount] = useState(0);
      const latestCountRef = useRef(count);
    
      useEffect(() => {
        // Store the latest count in the ref
        latestCountRef.current = count;
      }, [count]);
    
      useEffect(() => {
        const intervalId = setInterval(() => {
          // Access the latest count using the ref
          console.log('Latest Count:', latestCountRef.current);
        }, 1000);
    
        return () => clearInterval(intervalId);
      }, []);
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      );
    }
  4. Imperative Animations:

    • useRef can be used to perform imperative animations or operations. You can use it to store and update values related to animations or transitions.
    import React, { useRef } from 'react';
    
    function MyComponent() {
      const elementRef = useRef(null);
    
      const startAnimation = () => {
        elementRef.current.style.transform = 'translateX(200px)';
      };
    
      return (
        <div>
          <div ref={elementRef} className="box"></div>
          <button onClick={startAnimation}>Start Animation</button>
        </div>
      );
    }
  5. Caching Function References:

    • You can use useRef to cache function references, especially when dealing with dependencies in useEffect. This helps ensure that the effect uses the latest version of a function.
    import React, { useRef, useEffect } from 'react';
    
    function MyComponent() {
      const myCallback = () => {
        // ...
      };
    
      const myCallbackRef = useRef(myCallback);
    
      useEffect(() => {
        // Access the latest callback using the ref
        myCallbackRef.current();
      }, [myCallbackRef]);
    
      return <button onClick={myCallback}>Trigger Callback</button>;
    }

These are some common use cases for useRef in React. It's a versatile Hook that allows you to work with DOM elements, persist values, and address various other scenarios where mutable references are required.

6. Limitations of React

React is a popular and widely-used JavaScript library for building user interfaces, but like any technology, it has its limitations. Here are some limitations and challenges associated with React:

  1. Learning Curve: React has a learning curve, especially for developers new to component-based architecture and JSX (JavaScript XML). Concepts like props, state, and component lifecycle can take time to grasp.

  2. Boilerplate Code: While React simplifies many aspects of building user interfaces, it can still require boilerplate code, especially when handling state and props between components.

  3. State Management: React provides local component state, but for complex state management (e.g., global state, asynchronous data handling), you may need to use additional libraries like Redux or Mobx, which can add complexity.

  4. JavaScript Dependencies: React relies on JavaScript, so projects with complex UI logic may require additional JavaScript libraries and tools for tasks like routing, form handling, and data fetching.

  5. SEO: React applications initially rendered on the client side (CSR) can face SEO challenges because search engines may not properly index JavaScript-rendered content. Server-side rendering (SSR) can address this but adds complexity.

  6. Performance Optimization: While React is efficient, writing high-performance applications may require understanding optimization techniques like memoization, shouldComponentUpdate, and React's PureComponent.

  7. Tooling: Setting up and configuring a React project, especially with modern tooling (e.g., Webpack, Babel, ESLint), can be complex for beginners.

  8. Fragmentation: React's ecosystem has many libraries and tools, leading to fragmentation. This can make it challenging to choose the right packages and configurations for a project.

  9. Large Bundle Sizes: React itself is lightweight, but when combined with various dependencies and features, the bundle size of a React application can become large. Code-splitting and optimization techniques can help mitigate this issue.

  10. Community and Ecosystem Changes: React's ecosystem evolves rapidly, and libraries or best practices that are popular today may become outdated or deprecated. Staying up-to-date can be challenging.

  11. Native Mobile Development: While React Native allows for cross-platform mobile app development, it may not provide a native-like experience for complex applications compared to native development.

  12. Data Fetching: Fetching and managing asynchronous data in React components can be challenging, especially when dealing with complex data flows or handling edge cases.

  13. Testing: Writing comprehensive tests for React components, especially for component interactions and behavior, can be time-consuming.

7. Script Async & Defer

async and defer are attributes used in HTML when including external JavaScript files (<script> tags) to control how the script is loaded and executed. They are used to improve page loading performance and script execution order.

Here's an explanation of both attributes:

  1. async Attribute:

    • When you include a script with the async attribute, the script is downloaded asynchronously (in parallel) while the HTML parsing continues.
    • The script can be executed as soon as it's downloaded, even if the HTML parsing is not yet complete.
    • It doesn't block HTML parsing or rendering, which can lead to faster page loading.
    <script src="example.js" async></script>
    • Use async when the script doesn't depend on other scripts or the order of execution doesn't matter.
  2. defer Attribute:

    • When you include a script with the defer attribute, the script is also downloaded asynchronously, but it's not executed until the HTML parsing is complete.
    • The script is executed in the order in which it appears in the HTML document, right before the </body> tag, preserving the order of execution.
    • It doesn't block HTML parsing but ensures that the script doesn't run until the document is ready.
    <script src="example.js" defer></script>
    • Use defer when the script relies on the DOM structure, other scripts, or libraries loaded earlier in the document.

Key Differences:

  • Both async and defer scripts are downloaded asynchronously, but defer ensures ordered execution.
  • async scripts can execute while HTML parsing is still in progress, potentially leading to faster page loading.
  • defer scripts execute in the order they appear in the document, after HTML parsing is complete.

Considerations:

  • If you have scripts that depend on other scripts, you should carefully order and use defer or async as needed to ensure proper execution.
  • If script execution order is critical, use defer to maintain that order.
  • Keep in mind that using defer or async for all scripts might not be suitable for all situations. Consider the dependencies and loading behavior of your scripts to make an informed choice.

In modern web development, asynchronous loading and performance optimizations are crucial for delivering fast web experiences, and using async and defer attributes appropriately can contribute to achieving these goals.

8. Throttling & Debouncing

Throttling and debouncing are two techniques used in web development to manage and control the rate at which a function is executed, especially in response to events like scrolling, resizing, or user input. These techniques help improve performance and reduce the number of function calls in situations where events are triggered frequently. However, they serve different purposes and have distinct use cases:

Throttling:

  • Purpose: Throttling limits the rate at which a function can be called. It ensures that a function is called at a specified interval, preventing it from running too often.
  • Behavior: In throttling, the function is executed at regular intervals, even if the underlying event continues to occur. For example, if you throttle a scroll event handler to run every 100 milliseconds, it will execute every 100 milliseconds, regardless of how frequently the user scrolls.
  • Use Cases: Throttling is useful when you want to limit the frequency of function calls, ensuring that they don't overwhelm system resources or the user interface. Common use cases include scroll event handlers, resizing event handlers, and preventing rapid button clicks.
  • Implementation: Throttling is typically implemented by using a timer (e.g., setTimeout) to delay function execution until a specified interval has passed since the last invocation of the function.

Debouncing:

  • Purpose: Debouncing delays the execution of a function until a certain amount of time has passed since the last time the event occurred. It ensures that the function is executed only once after a pause in the events.
  • Behavior: In debouncing, the function is executed once, but the execution is delayed until the event hasn't occurred for a specified duration. For example, if you debounce a search input handler with a delay of 300 milliseconds, the search function will run only if the user stops typing for 300 milliseconds.
  • Use Cases: Debouncing is useful when you want to respond to an event that can occur frequently but should trigger a single action after a brief pause. Examples include search input handling, autocomplete suggestions, and preventing multiple API requests for rapid user input.
  • Implementation: Debouncing is typically implemented using timers (e.g., setTimeout or clearTimeout) to delay function execution and reset the timer each time the event occurs.

In summary, throttling limits the rate of function calls, ensuring they occur at regular intervals, while debouncing delays function execution until a pause in the events. The choice between throttling and debouncing depends on the specific behavior you want to achieve in response to certain events in your web application.

9. Error boundary

Error boundaries are a feature in React that allows you to catch and handle errors that occur during rendering, in lifecycle methods, or during event handling within the components below the error boundary. They help prevent unhandled JavaScript errors from crashing your entire application and provide an opportunity to display a fallback UI or report the error.

In a React class component, you can create an error boundary by defining a special method called componentDidCatch. However, as of my last knowledge update in September 2021, error boundaries are not supported in functional components using hooks directly. You would typically use class components for creating error boundaries. Here's an example using a class component:

import React, { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, errorInfo) {
    // Handle the error, log it, and/or report it to a monitoring service
    console.error('Error:', error);
    console.error('Error Info:', errorInfo);
    this.setState({ hasError: true });
  }

  render() {
    if (this.state.hasError) {
      // Render a fallback UI when an error occurs
      return <div>Something went wrong. Please try again later.</div>;
    }

    return this.props.children;
  }
}

// Usage of the error boundary in a parent component
function ParentComponent() {
  return (
    <ErrorBoundary>
      {/* Child components */}
      <ChildComponent />
    </ErrorBoundary>
  );
}

// A component that may throw an error
function ChildComponent() {
  // Simulate an error
  throw new Error('This is a simulated error.');
}

export default ParentComponent;

Please note that error boundaries are typically used with class components because they rely on the componentDidCatch lifecycle method. Functional components can still handle errors within their own context using JavaScript try-catch blocks, but they don't provide the same global error handling capability as error boundaries in class components. If there have been changes or additions to error boundary functionality in React after my last knowledge update, you should consult the React documentation for the most up-to-date information.

10.

⚠️ **GitHub.com Fallback** ⚠️