Core Concepts and Fundamentals - rs-hash/Senior GitHub Wiki

Explain the Virtual DOM in React.

  • The Virtual DOM is a lightweight, in-memory representation of the actual DOM. It's a JavaScript object that mirrors the structure of the real DOM.
  • When a React component is rendered for the first time, React creates a virtual representation of the DOM called the Virtual DOM.
  • This Virtual DOM is a JavaScript object that contains information about each element in the component tree.
  • When state or props change in a component, React triggers a re-render of the component.
  • Instead of directly updating the real DOM, React first updates the Virtual DOM by creating a new Virtual DOM tree.
  • React then performs a diffing algorithm to identify the differences (or changes) between the old Virtual DOM and the new Virtual DOM.
  • React compares the old Virtual DOM tree with the new Virtual DOM tree to find the minimal set of changes needed to update the real DOM.
  • This process is known as reconciliation or "diffing."
  • By identifying only the changed elements, React minimizes the number of actual DOM manipulations required.
  • After the differences are identified, React updates the real DOM efficiently by applying only the necessary changes.
  • React uses efficient algorithms and batch updates to optimize DOM operations, resulting in improved performance.

Benefits of the Virtual DOM

  • Performance Optimization: The Virtual DOM reduces the number of DOM manipulations, which can be expensive in terms of performance.
  • By batch updating and minimizing DOM changes, React improves rendering performance and responsiveness.
  • React's diffing algorithm ensures that only the necessary updates are applied to the real DOM, reducing unnecessary re-renders and improving overall rendering efficiency.
  • Immutable Data: React relies on immutability for efficient Virtual DOM diffing. Avoid mutating state or props directly to ensure accurate change detection.

What are the differences between functional components and class components?

Class components

  • Class components are JavaScript classes that extend the React.Component class and have a render method.
  • They are defined using the class keyword and extend React.Component
  • Class components traditionally used this.state and setState for managing component state.
  • Class components have access to lifecycle methods such as componentDidMount, componentDidUpdate, componentWillUnmount, etc.
  • Class components can define class properties like this.state and this.props directly within the class scope.
  • Performance: Slightly less efficient compared to functional components due to their heavier nature.

LIFECYCLE METHODS

  • Lifecycle methods are available only in class components and can be used to manage state in various stages of the component's lifecycle.
  • Each component has several lifecycle methods such as componentDidMount, componentDidUpdate, componentWillUnmount.

Mounting phase

  • constructor() - called when the component instance is created
  • static getDerivedStateFromProps(props, state)
  • Invoked right before rendering when new props or state are received
  • it is called after constructor() and before render()(static method )
  • Since getDerivedStateFromProps is a static method, it does not have access to this.
  • The main purpose of getDerivedStateFromProps is to allow a component to update its internal state based on changes in props. It's important to note that this method should be used sparingly and only when the state absolutely needs to be updated based on props.
  • render() - Renders the component's UI based on current props and state.
  • componentDidMount()
  • Called after the component has been rendered to the DOM.
  • Ideal for initializing third-party libraries, fetching data from APIs, or setting up subscriptions.

Updating phase

  • static getDerivedStateFromProps(nextProps, prevState)
  • Similar to the mounting phase method but called before each render when new props or state are received.
  • shouldComponentUpdate() - Determines if the component should re-render based on changes in props or state.
  • render(): Re-renders the component's UI based on updated props and state.
  • getSnapshotBeforeUpdate(prevProps, prevState)
  • Called right before the DOM is updated after a component's render.
  • Used for capturing information from the DOM (e.g., scroll position) before it changes
  • componentDidUpdate(prevProps, prevState, snapshot)
  • Invoked after the component's update and the DOM has been updated.
  • Useful for interacting with the updated DOM, handling side effects, or updating state based on prop changes.

Unmounting phase

  • componentWillUnMount() - called just before the component is removed from the DOM

Usage and Best Practices:

  • Use constructor for initializing state, binding methods, and setting initial values.
  • componentDidMount is suitable for initializing third-party libraries, fetching data, or setting up subscriptions.
  • Implement shouldComponentUpdate to optimize re-renders by preventing unnecessary updates.
  • Use PureComponent or React.memo for functional components to automatically perform shallow prop and state comparisons.
  • Perform side effects (e.g., network requests, DOM updates) in componentDidMount, componentDidUpdate, and componentWillUnmount.
  • Use getSnapshotBeforeUpdate for capturing information from the DOM before it changes.
  • Perform cleanup tasks (e.g., unsubscribing from subscriptions, clearing timers) in componentWillUnmount to avoid memory leaks.

Functional components

  • Functional components are JavaScript functions that take props as input and return JSX elements.
  • They are defined using arrow functions or regular functions.
  • Prior to React 16.8, functional components lacked lifecycle methods but now can use useState, useEffect and other hooks for similar functionality.
  • With the introduction of React Hooks, functional components gained full state and lifecycle management capabilities
  • Functional components are often more concise and easier to read, especially for simple UI components.

HOOKS

  • React hooks added to React in the 16.8 version. It allows to manage the state and side effects in functional components. Hooks allow us to write functional React components and still be able to hook into all of the React component functionality, including lifecycle methods.
  • Hooks can be only called in React functional components and cannot be conditional and must be called at the top level of a function component or another custom hook.
  • But the new "use" hook (React 19 canary) can be called within loops and conditional statements like if. Various types of hooks are present like
  • State Hooks - useState, useReducer
  • Context Hooks - useContext
  • Ref Hooks - useRef
  • Effect Hooks - useEffect
  • Performance Hooks - useMemo, useCallback, useTransition, useDeferredValue
  • Resource Hooks - use
  • Others - useId, useDebugValue

How does React handle state management?

  • React state management is a process for managing the data that React components need in order to render themselves.
  • This data is typically stored in the component's state object.
  • When the state object changes, the component will re-render itself.

Ways of Handling State in React using Hooks:

  • useState: Manages local component state within functional components.

  • useReducer: Alternative to useState for managing complex state logic using reducers.

  • useContext: Consumes context values within functional components for global state management.

  • useRef: Creates a mutable object that persists across renders, useful for accessing DOM elements.

  • useEffect: Handles side effects and lifecycle methods in functional components.

Handling State in Bigger Applications:

  • Component Composition: Break down your application into smaller, reusable components to isolate state management.

  • Context API: Use useContext to manage global or shared state efficiently across the component tree.

  • State Management Libraries: Consider using libraries like Redux or MobX for complex state management needs in large-scale applications.

Best Practices for State Management:

  • Keep State Minimal: Store only necessary data in state to avoid unnecessary re-renders and improve performance.

  • Immutability: Follow immutability principles when updating state to ensure predictable component behavior and avoid side effects.

  • Separation of Concerns: Separate UI state from business logic for better code organization and maintenance.

  • Component Composition: Use composition patterns to build reusable and composable components, reducing the complexity of state management.

When to Use Each Approach:

  • useState: Use for managing local component state within functional components.

  • useReducer: Use for complex state logic or when state transitions involve multiple variables.

  • useContext: Use for managing global or shared state across multiple components without prop drilling.

  • useRef: Use for accessing and managing DOM elements or for persisting values across renders.

  • useEffect: Use for handling side effects, such as data fetching, and for mimicking lifecycle methods in functional components.


How do you decide when to use a state management tool (e.g., Redux) in your app? When using one, is there any data you store in other places, and why?

Redux is most useful in cases when: ( Before React 16.3)

  • different parts of my app need to use the same state at the same time
  • I would normally have to lift that state up maybe all the way to the root app component in order for many components to share the data. But if I do that, I would then have to prop-drill and pass that data as props through every level of the component tree
  • that is a reason why many people picked Redux in ’15, ’16, ’17.

Redux and React Context now

  • with React 16.3, React came out with a new, improved Context API
  • only purpose of Context is to act as a dependency injection mechanism scoped to some portion of your subtree, where you say “Here is a value”, and any portion of that component subtree can ask to read the value.
  • So if the only thing you needed to do with Redux is avoid passing data as props through 15 levels of your components - well, that’s literally what Context was invented to do.So, if that’s all you need, then you don’t need to add Redux just to add that capability; use Context instead.

“Should I use Context or should I use Redux?”

  • And they seem to think that Context itself is a state management system. It’s not.

  • It’s a dependency injection mechanism, and you can put whatever value you want in Context, and most often you are the one managing that state in a React component, with the useState hook or the useReducer hook. And you’re the one deciding where the state lives, handling how to update it, and then putting the value into Context for distribution.

  • So yeah, useReducer plus useContext together kind of make up a state management system. And that one is more equivalent to what Redux does with React, but Context by itself is not a state management system.

Redux and other tools

  • if the only thing you were doing with Redux was storing cache data from the server, and you choose to use GraphQL and you choose to use React Query, then you’ve just fulfilled the use case that you were previously choosing to use Redux for, and for that situation you don’t need Redux.

Data Fetching with REDUX vs REACT QUERY

Redux

  • Centralized state management with Redux store.
  • Full control over state transitions and actions.
  • Can integrate with middleware like Redux Thunk or Redux Saga for asynchronous actions.
  • Requires additional boilerplate code for action creators, reducers, and handling async actions.
  • Doesn't provide built-in caching or automatic refetching.
  • Complexity increases with more complex data fetching scenarios.

React Query

  • Simplified data fetching with useQuery hook, reducing boilerplate code.
  • Built-in caching, automatic refetching, and error handling.
  • Provides a global query cache for managing server data efficiently.
  • Limited to client-side data fetching and caching, not a full state management solution.
  • May require additional setup for complex data fetching scenarios or mutations.

REDUX & GRAPHQL

  • GraphQL is a query language and runtime for APIs that allows clients to request specific data from the server.
  • It provides efficient data fetching, minimizes over-fetching and under-fetching of data, and supports real-time updates through subscriptions.
  • GraphQL is a powerful tool for optimizing data fetching and manipulation between the client and server.
  • Redux and GraphQL serve different purposes, with Redux focusing on state management and GraphQL focusing on efficient data fetching and manipulation.

Redux

Redux is a very generic state management tool that can be used for a broad array of use cases.

  • Caching state from a server
  • UI state
  • other complex data management on the client

To decide if we need a state management tool for a specific application, we may need to consider various factors which include

  • Do we need to cache state from a server and are we using GraphQL or another library to cache?
  • Do other parts of the application require data ?
  • Is the same data being used in multiple components?
  • Do we need time travel debugging ?
  • Do we need to store the data consistently when components state update?
  • So we have to decide based on the answers if we need Redux / not
  • If the application's state is complex and if it's needed across the app and or if the logic is complex to maintain,if we need to cache the data we use a state management tool like Redux. It's best to use if the application requires scalability and maintainability.
  • However, if the data is not required to share across the application and is specific to a component and if we already have GraphQL / React Query for caching, in those cases we can use local state / useContext + useReducer .
  • useReducer plus useContext together can make up a simple state management system

What are props in React and how are they used?

Props (short for properties) are one of the fundamental concepts in React. They enable components to receive data from their parent components, making components more dynamic and reusable.

  • Data Flow: Props enable a unidirectional data flow from parent to child components, ensuring that data is passed down the component tree in a predictable manner.
  • Component Reusability: By passing different props to the same component, you can reuse components across your application with different data.
  • Read-Only: Props are immutable. Once passed to a child component, they cannot be modified by the child. This ensures that the data flow remains predictable and unidirectional.
  • Dynamic: Props can change over time as the parent component's state changes, allowing child components to update dynamically.
  • Pass Data: Props can pass any type of data (strings, numbers, arrays, objects, functions) to a child component.
  • Conditional Rendering: Props can be used to conditionally render components or parts of a component based on the data received.
  • Event Handlers: Props can pass functions that act as event handlers, enabling child components to trigger actions in the parent component.
  • To achieve upward data flow, you pass a callback function from the parent as a prop to the child, which the child can call with the required data.
  • Passing large or complex objects as props can affect performance. Consider memoizing components using React.memo to prevent unnecessary re-renders.

Explain the concept of controlled vs. uncontrolled components.

Controlled Components

Controlled components are form elements (like , <textarea>, and ) whose values are controlled by the React state. This means that the form element's value is driven by the component's state, and any changes to the input are handled through event handlers that update the state.

import React, { useState } from 'react';

const ControlledForm = () => {
  const [inputValue, setInputValue] = useState('');

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitted value:', inputValue);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={inputValue} onChange={handleChange} />
      <button type="submit">Submit</button>
    </form>
  );
};

export default ControlledForm;

Uncontrolled components

Uncontrolled components are form elements that manage their own state internally. Instead of using the component state to control the value, you access the value using refs to get the current value directly from the DOM.

import React, { useRef } from 'react';

const UncontrolledForm = () => {
  const inputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitted value:', inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" defaultValue="default" ref={inputRef} />
      <button type="submit">Submit</button>
    </form>
  );
};

export default UncontrolledForm;

Controlled Components are preferable when you need:

  • Real-time validation
  • Immediate feedback based on input changes
  • Tight integration with other component state

Uncontrolled Components can be useful when:

  • You don't need to control every state change
  • The form is simple and does not require complex validation or immediate feedback
  • Minimizing code complexity is more important than real-time control

What are higher-order components (HOCs) and when are they useful?

Higher-Order Components (HOCs) are an advanced pattern in React for reusing component logic. An HOC is a function that takes a component and returns a new component with enhanced functionality. This pattern allows for code reuse, logic abstraction, and better separation of concerns.

Key Characteristics of HOCs

  • Function Signature: An HOC is a function that accepts a component as an argument and returns a new component.
  • Enhanced Components: The returned component typically wraps the original component, adding new behaviors or props.
  • Pure Functions: HOCs do not modify the original component. Instead, they create a new component by composition.

Common Use Cases for HOCs

  • Access Control: Wrap components to restrict access based on user roles or permissions.
  • Logging: Add logging functionality to track component rendering or user interactions.
  • Theming: Apply consistent theming across multiple components by injecting theme-related props.

Using HOCs with Functional Components

import React from 'react';

// Higher-Order Component
const withLoading = (WrappedComponent) => {
  return function EnhancedComponent(props) {
    const { isLoading, ...otherProps } = props;
    if (isLoading) {
      return <div>Loading...</div>;
    }
    return <WrappedComponent {...otherProps} />;
  };
};

// Functional Component
const MyComponent = ({ data }) => {
  return <div>Data: {data}</div>;
};

// Enhanced Component
const MyComponentWithLoading = withLoading(MyComponent);

const App = () => {
  const [loading, setLoading] = React.useState(true);
  const data = "Here is some data";

  React.useEffect(() => {
    setTimeout(() => setLoading(false), 2000);
  }, []);

  return <MyComponentWithLoading isLoading={loading} data={data} />;
};

export default App;

Using Hooks Instead of HOCs

import React from 'react';

// Custom Hook for Loading
const useLoading = (initialLoading = true) => {
  const [isLoading, setLoading] = React.useState(initialLoading);

  React.useEffect(() => {
    setTimeout(() => setLoading(false), 2000);
  }, []);

  return isLoading;
};

// Functional Component with Hook
const MyComponent = ({ data }) => {
  const isLoading = useLoading();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return <div>Data: {data}</div>;
};

const App = () => {
  const data = "Here is some data";

  return <MyComponent data={data} />;
};

export default App;
  • While HOCs are a powerful pattern for reusing logic in React components, the introduction of hooks has made managing state and side effects in functional components more straightforward and composable.
  • Hooks are generally preferred for new codebases due to their simplicity and flexibility.
  • However, HOCs remain a valuable tool in certain scenarios, especially in maintaining consistency in legacy code or integrating with third-party libraries

How does React Router work, and what are its key components?

React Router enables "client side routing".

  • Client side routing allows your app to update the URL from a link click without making another request for another document from the server. Instead, your app can immediately render some new UI and make data requests with fetch to update the page with new information.

  • This enables faster user experiences because the browser doesn't need to request an entirely new document or re-evaluate CSS and JavaScript assets for the next page. It also enables more dynamic user experiences with things like animation.

  • Client side routing is enabled by creating a Router and linking/submitting to pages with Link and

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