Skip to content
Web and Mobile Development

React interview questions and answers

72 questions, 16 with code. About 4 h 13 min to read them all.

By TestLab · Updated

Plan for an interview
72 questions

Track what you know and what to revise.

Sign in to track progress

You can open a few full answers without signing in. Sign in to read all

1

Could you describe the React library and explain the key reasons behind its widespread adoption for building user interfaces?

React is a declarative JavaScript library for building user interfaces, centered around a component-based architecture that allows developers to construct encapsulated UI pieces managing their own state. It is used because it simplifies the creation of complex, interactive web applications by enabling efficient updates through reconciliation and fostering code reuse across different parts of a project.

Read full answer

Applies toReact 19

Core Concepts

  • Components: Reusable functions that return JSX, accepting inputs called props and managing internal state via hooks.

  • JSX: A syntax extension resembling HTML that compiles to standard JavaScript function calls, making UI structure explicit.

  • State and Props: Props flow downward from parent to child, while state is internal data that triggers re-renders when changed.

  • Reconciliation: React calculates the minimal changes needed to update the actual DOM, ensuring performance without manual manipulation.

Why Developers Choose React

  • Declarative Style: Developers describe what the UI should look like for each state, rather than imperatively manipulating the DOM.

  • Ecosystem: A vast library of third-party packages and strong community support accelerates development and troubleshooting.

  • Performance: Features like concurrent rendering and automatic batching in React 19 allow the app to remain responsive during heavy computations or data fetching.

  • Cross-Platform: Knowledge of React transfers to React Native, enabling mobile development with similar patterns.

Modern React Development

In React 19, function components with hooks are the standard, while class components are considered legacy and should be avoided in new projects. New features include the use hook for reading promises and resources directly in components, and Server Actions for handling form submissions on the server. Server Components and Server Actions offer enhanced capabilities but require a framework like Next.js App Router to function, as they are not available in plain React client rendering. Additionally, Create React App is deprecated; new projects should initialize using Vite or a full-stack framework like Next.js.

Best Practices

  • Follow Rules of Hooks: Only call hooks at the top level of components or custom hooks to preserve state consistency.

  • Use TypeScript: Pairing React with TypeScript provides type safety for props and state, reducing runtime errors.

  • Avoid Class Components: Unless maintaining legacy code, stick to function components for better compatibility with modern features.

  • Select Tooling Wisely: Use Vite for client-only apps and Next.js for server-rendered or static sites to ensure optimal performance.

2

Define what constitutes a component in React and explain its purpose in building user interfaces.

A React component is a reusable JavaScript function that returns UI elements rendered by React, serving as the fundamental building blocks for constructing user interfaces in modern applications. In contemporary React development, components are written as function components using hooks rather than legacy class-based approaches.

Read full answer

Applies toReact 19

Core Definition

Components encapsulate markup, logic, and styling into self-contained units. Each component can be thought of as a custom HTML element with its own properties and internal state management capabilities. They enable developers to decompose complex user interfaces into manageable, testable pieces.

Component Types

In React 19, there are several distinct component types you should understand:

  • Function Components: The standard approach where components are simple JavaScript functions that accept props as an object parameter and return JSX

  • Class Components: An older syntax using ES6 classes; now considered legacy and not recommended for new code in favor of function components with hooks

  • Server Components: Available only within frameworks like Next.js App Router, allowing rendering on the server side to optimize performance

Component Characteristics

Key features define effective React components:

  • Props Immutability: Component input through props remains read-only, preventing direct modification from parent components

  • Unidirectional Data Flow: Data flows down through props, creating predictable state updates throughout the component tree

  • Composition Over Inheritance: Components combine other components rather than extending them through traditional inheritance hierarchies

  • State Management: Function components use hooks like useState to track data that changes over time

Lifecycle Management

Modern function components handle lifecycle events differently than class-based patterns. Since function components lack explicit lifecycle methods, they use hooks to replicate equivalent behavior:

Event Type Class Component Approach Function Component Approach
Mounting componentDidMount() useEffect(() => {}, [])
Updating componentDidUpdate() useEffect(() => {}, [dependencies])
Unmounting componentWillUnmount() Return cleanup function in useEffect
Initialization Constructor method Arguments to useState()

Understanding these differences ensures proper effect setup when working with modern codebases or transitioning from legacy patterns.

Best Practices

Follow these guidelines when building components:

  • Keep components single-purpose with narrow scope

  • Extract shared logic into custom hooks when reuse occurs across multiple components

  • Avoid deeply nested structures, preferring horizontal composition

  • Document component props with JSDoc comments or TypeScript interfaces

  • Prefer CSS-in-JS libraries or CSS modules over inline style objects

  • Test individual components independently before integration into larger contexts

Well-designed components prioritize reusability while maintaining clarity about their responsibilities and interactions with other parts of your application. This modular approach simplifies debugging and enables teams to work on different portions simultaneously without conflicts.

3

In modern React development, what is the standard approach to defining a reusable UI building block?

In React 19, you create a component by defining a JavaScript function that returns JSX, known as a function component. This approach replaces legacy class components and integrates directly with hooks for state and side effects.

Read full answer

Applies toReact 19

Defining a Function Component

A component is simply a function that accepts a props object and returns a React element (JSX). You typically export the function so it can be imported and used in other files. The function name must start with a capital letter so React distinguishes it from a standard HTML tag during compilation.

Managing State and Effects

To add interactivity, use hooks like useState for local state and useEffect for side effects. Hooks must be called at the top level of the component or within other hooks, following the Rules of Hooks. In React 19, you can also use new APIs like use to read promises or context, though useState remains the standard for mutable UI state.

Component Composition and Props

Components are composed by nesting them inside one another. Data flows down via props, which are read-only within the receiving component. You can pass children as a special prop to wrap content, enabling flexible layout structures. TypeScript interfaces are commonly used to define prop shapes for better developer tooling.

Best Practices and Tooling

Use Vite or a framework like Next.js for new projects instead of the deprecated Create React App. Remember that React Server Components require a framework that supports them, such as Next.js App Router, and cannot be used in standard client-only applications. Always ensure effects have correct dependency arrays to avoid stale closures or infinite loops.

4

Could you define JSX and explain the rationale for using it within React applications?

JSX (JavaScript XML) is a syntax extension that allows developers to write HTML-like markup directly within JavaScript code, which React transforms into standard JavaScript function calls. It is used because it makes the UI structure more readable, enables compile-time error checking, and integrates seamlessly with React’s component model and ecosystem tooling.

Read full answer

Applies toReact 19

What JSX Actually Is

JSX looks like HTML but is actually syntactic sugar for JavaScript. When you write <div className="box">Hello</div>, React does not render HTML strings directly. Instead, the compiler transforms this into React.createElement('div', { className: 'box' }, 'Hello'). This means JSX is fully JavaScript, allowing you to use variables, functions, and logic inside your markup. It is not a template engine; it is JavaScript that describes what the UI should look like.

Reasons for Adoption

Developers use JSX primarily for readability and maintainability. Visualizing the UI structure alongside the logic that controls it reduces cognitive load compared to separating templates and logic. It also enables better tooling support, such as autocompletion and type checking when using TypeScript (TSX). Furthermore, JSX facilitates component composition, making it natural to nest components within one another, which aligns with React’s declarative paradigm.

Compilation and Tooling

Browsers cannot understand JSX natively, so it must be compiled before reaching the browser. Modern tooling like Vite or Next.js handles this transformation during the build step using Babel or similar compilers. This compilation process also allows for optimizations, such as flagging invalid DOM properties or missing keys in lists at build time rather than runtime. In React 19, the compiler continues to support JSX while enabling newer features like Actions and Server Components within supported frameworks. The transformation maps syntax to function calls as shown below.

JSX Syntax Compiled JavaScript Equivalent
<div className="box">Hi</div> React.createElement('div', { className: 'box' }, 'Hi')
<Welcome name="Alice" /> React.createElement(Welcome, { name: 'Alice' })
<>Content</> React.createElement(React.Fragment, null, 'Content')

Best Practices and Pitfalls

When writing JSX, always use className instead of class because class is a reserved keyword in JavaScript. Similarly, use htmlFor instead of for. Embed expressions using curly braces {}, but avoid placing statements like if or for loops directly inside JSX; instead, compute them beforehand or use map for lists. Remember that only one root element can be returned from a component unless you use a Fragment (<>...</>) to group elements without adding extra nodes to the DOM.

When JSX Is Not Required

While JSX is the standard, it is technically possible to write React components without it using React.createElement directly. However, this is verbose and rarely done in modern applications. You might encounter non-JSX patterns in legacy code or specific programmatic generation scenarios, but for standard UI development in React 19, JSX remains the recommended approach for clarity and ecosystem compatibility.

5

Describe how React uses a virtual representation of the DOM and the process behind updating the actual browser DOM efficiently

React maintains an in-memory Virtual DOM representation that mirrors the real DOM tree, enabling efficient updates by minimizing costly browser operations through a comparison process called reconciliation.

Read full answer

Applies toReact 19 with function components and hooks (class components are legacy)

What Is the Virtual DOM

The Virtual DOM is a lightweight JavaScript object model that acts as a copy of the actual Document Object Model (DOM). Instead of manipulating the browser DOM directly every time component state changes, React first updates this lightweight representation. Each Virtual DOM node contains information about element types, props, and children, making comparisons significantly faster than touching the real DOM.

When any part of your application’s state changes, React performs the following steps:

  1. Creates a new Virtual DOM tree representing the current UI state

  2. Compares the new tree with the previous one using a diffing algorithm

  3. Calculates the minimum number of changes required

  4. Updates only those specific parts in the actual browser DOM

How Reconciliation Works

Reconciliation is React’s term for its update process. The diffing algorithm works on several principles:

  • Element Types: When two elements have different types, React destroys the old subtree and creates the new one entirely.

  • Keys: Developers can provide unique key props to help React identify which items changed, were added, or removed when dealing with lists.

  • Component Identity: React preserves state and DOM nodes only when the same component type renders again. If the component type changes—for example, swapping one component for another—React unmounts the previous component and mounts the new one. Legacy class components follow the same rule, but new React 19 development should use function components and hooks.

Aspect Direct DOM Manipulation React Virtual DOM
Performance Cost High - triggers reflow/repaint each time Optimized - batched updates only
Update Granularity Manual tracking required Automatic diffing
Predictability State can diverge from intent State always matches rendered output
Browser Interference Multiple small writes slow rendering Single synchronized commit phase

Key Benefits

Performance Optimization: Updating the real DOM is expensive due to layout calculations and paint operations. By batching changes and applying them together, React reduces these costly operations.

Development Model: Developers focus on describing what the UI should look like for any given state rather than writing imperative commands like document.getElementById().innerHTML = ....

Consistency: Since all state changes go through React’s system, the UI never gets out of sync with the underlying data model.

Limitations and Considerations

The Virtual DOM adds overhead compared to direct DOM manipulation for extremely simple applications. For cases where you need maximum performance with frequent low-level updates, consider whether React’s abstraction layer adds too much cost. However, for most applications, the optimization benefits far outweigh this overhead.

Large-scale renderings can still cause performance issues if not managed properly. Using keys appropriately, memoizing components that don’t change often, and avoiding unnecessary state updates help maintain performance at scale.

Best Practices

Never rely on array indices as keys unless the list is static. Use stable identifiers like database IDs instead. Avoid creating new objects inline within JSX expressions because they cause unnecessary re-renders—use useMemo for computed values and useCallback for functions passed as props to child components that expect referential stability.

6

How do React class components and functional components differ in implementation and feature support?

The fundamental difference lies in syntax and state handling, where functional components utilize JavaScript functions with Hooks to manage state and side effects, while class components depend on ES6 classes extending React.Component with specific lifecycle methods. Functional components are the modern standard, whereas class components are legacy and discouraged for new code.

Read full answer

Applies toReact 19

Syntax and Structure

Class components require defining a class that extends React.Component and implementing a render() method that returns JSX. They rely on the this keyword to access props, state, and context, which often necessitates manual binding of event handlers in the constructor. In contrast, functional components are plain JavaScript functions that return JSX directly, accepting props as arguments without needing this binding or a dedicated render method.

State and Lifecycle Management

In class components, state is initialized in the constructor via this.state and updated using this.setState(), which merges objects asynchronously. Lifecycle methods such as componentDidMount, componentDidUpdate, and componentWillUnmount handle side effects separately by concern. Functional components replace these with Hooks: useState manages local state, and useEffect consolidates lifecycle logic into a single hook that runs after render based on dependency arrays. This reduces boilerplate and colocalizes related logic—e.g., data fetching, subscriptions, or DOM updates can all live together in one useEffect rather than being scattered across multiple lifecycle methods.

Performance and Optimization

Functional components generally offer better performance potential due to their lighter weight and compatibility with modern optimizations. React can optimize functional components more easily using React.memo, useMemo, and useCallback to prevent unnecessary re-renders. Class components rely on shouldComponentUpdate or React.PureComponent for similar control, which is more verbose and harder to maintain accurately. Additionally, functional components enable modern features like Concurrent Rendering and Server Components (in frameworks like Next.js), which are unavailable to class components since Server Components require a framework with SSR capabilities.

Comparison Summary

Aspect Functional Components Class Components
Definition Plain JavaScript function ES6 class extending React.Component
State useState Hook this.state in constructor + this.setState()
Side Effects useEffect Hook Lifecycle methods (componentDidMount, etc.)
Binding Props passed as arguments; no this required Requires manual binding of this in constructor
Optimization React.memo, useMemo, useCallback shouldComponentUpdate, React.PureComponent
Modern Features Full support including Concurrent Features, Server Components No support for Server Components or new concurrent APIs
Future Status Current standard Legacy, backward compatible only, no new features

Migration and Best Practices

New projects should exclusively use functional components with Hooks. Existing class components should be migrated gradually when touched, focusing on converting state and lifecycle logic to useState and useEffect. Avoid mixing patterns within the same component tree where possible, and never attempt to use Hooks inside class components, as this violates the Rules of Hooks and will throw runtime errors. Class components remain supported in React 19 for backward compatibility but receive no new features. When migrating, convert stateful logic first, then lifecycle effects, testing thoroughly at each step since useEffect differs significantly from traditional lifecycle behavior—the dependency array must match the original lifecycle method’s trigger conditions exactly.

7

How are user interactions such as clicks or input changes managed within React components?

React manages user interactions through a Synthetic Event System that normalizes native browser events across different platforms and provides a consistent interface regardless of the underlying DOM implementation. Developers attach handlers using camelCase props like onClick or onChange on JSX elements, passing functions that execute when the interaction occurs.

Read full answer

Applies toReact 19

Event Naming and Syntax

In JSX, event names use camelCase rather than lowercase HTML attributes. For example, onclick becomes onClick and onchange becomes onChange. Handlers are assigned by passing a function reference, not a string, which allows logic to reside in JavaScript rather than markup. This distinction prevents accidental execution during render and keeps the declarative UI structure intact.

Accessing Event Data

The handler receives a synthetic event object as the first argument. This object wraps the native event and provides cross-browser compatibility. Properties like target and currentTarget are accessible synchronously. Modern React automatically handles event pooling concerns, making it safe to access synthetic event properties within handlers and within any synchronous operations they call.

Optimization and Stability

To prevent unnecessary child re-renders, wrap handlers in useCallback when passing them down as props to memoized components. This memoizes the function identity across renders unless dependencies change. For local state updates within the same component, direct function definitions are usually sufficient and stable enough without additional hooks overhead.

Code Example

import { useState, useCallback } from 'react';

export function FormComponent() {
  const [input, setInput] = useState('');

  const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setInput(e.target.value);
  }, [setInput]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Submitted:', input);
  };

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

Common Pitfalls

Avoid invoking the handler during render (e.g., onClick={handleClick()}) which executes immediately instead of on click. Remember that Server Components in frameworks like Next.js cannot contain interactive event handlers; those components must be marked as Client Components using the 'use client' directive. Always call preventDefault() on form submissions when you need to prevent the page reload behavior that forms normally cause.

8

How do state and props function within React components, and what distinguishes them from each other?

In React, state is internal data owned by a component that can change over time and trigger UI updates, whereas props are read-only arguments passed from a parent to a child; understanding which piece of data belongs in state versus props is essential for predictable rendering and clean component design.

Read full answer

Applies toReact 19 with function components and hooks

Modern Component Model

In React 19, function components with hooks are the standard; class components are legacy. State and props operate within this model: props arrive as function parameters, and state is introduced via useState or other state hooks.

How State Works

  • useState(initialValue) returns [value, setValue]; the setter schedules a re-render rather than changing the value synchronously.

  • State is preserved across re-renders and is private to the component instance unless lifted or shared through Context.

  • Updates can be batched, so reading count immediately after setCount may still return the old value; use the functional form setValue(prev => ...) when the new value depends on the previous one.

  • Initial state may be computed lazily by passing a function, which avoids expensive calculations on every render.

  • State should be minimal: avoid storing values that can be computed from other state or props during render.

How Props Work

  • Props are the inputs to a component, similar to function arguments, and include data, event callbacks, children, and configuration such as className.

  • A component must never mutate its own props; if data needs to change, the owner parent updates its state and passes new props down.

  • Props enable composition: the same component can render differently depending on what it receives, without knowing its parent.

  • Default values can be supplied through destructuring defaults or a parent’s JSX, keeping the child independent.

  • Because props are received during every render, they reflect the parent’s latest state.

State vs Props Comparison

Aspect State Props
Ownership Internal to the component Owned by the parent
Mutability Changed only via a setter function Read-only within the child
Update cause Local event, effect, or callback Parent re-render or new values
Scope Component instance Passed down the tree
Purpose Tracks data that changes over time Configures and connects components
Initialization useState initializer JSX attributes or parent state

Data Flow Patterns

  • Lift state up: When siblings or a parent and child share data, move the state to their closest common ancestor and pass it down as props.

  • Controlled components: A form input often receives its value via props and reports changes through a callback, so the parent remains the source of truth.

  • Context: For values needed by many components at different levels, Context avoids passing props through intermediate layers.

  • Derived values: Compute values during render from state and props instead of storing them in state, preventing stale or inconsistent data.

Common Pitfalls

  • Direct mutation: Writing state.x = 1 does not trigger a re-render; always use the setter.

  • Stale closures: Capturing state in an event handler or effect can yield old values; prefer functional updates or dependency arrays.

  • Prop drilling: Passing props through many unrelated components makes code fragile; use Context or a state library when appropriate.

  • Unnecessary state: Storing data that can be derived adds synchronization burden and bugs.

  • Hook violations: Call useState and other hooks at the top level of the component, never inside loops, conditions, or nested functions.

9

What mechanisms does React provide for sharing data and state between components in a modern application?

In React, data flow is unidirectional, primarily achieved through props passed from parent to child components. For global or deeply nested state, the Context API or external state management libraries are used to avoid prop drilling. Communication from child to parent is handled by passing callback functions down as props.

Read full answer

Applies toReact 19 with function components and hooks.

Props and Callbacks

The standard pattern for local data sharing involves passing values via props. When a child needs to modify state owned by a parent, the parent passes a handler function down. This keeps the source of truth at the highest necessary level. For example, a form input component might receive an onChange handler to notify the parent of user input changes. Composition is also preferred over inheritance for code reuse, allowing components to accept children elements.

Context API

When many components need access to the same data, passing props through intermediate layers becomes cumbersome (prop drilling). React’s Context API allows you to broadcast data to any component in the tree without explicit prop passing. You create a context object, wrap consumers in a Provider, and consume values using the useContext hook. This is ideal for themes, authentication status, or locale settings. Ensure providers are placed high enough in the tree to cover all consumers.

External State Management

For complex client state involving frequent updates or derived data, libraries like Zustand or Redux Toolkit are often preferred over Context. Context can cause unnecessary re-renders if not optimized with memoization. External libraries provide better tooling for debugging, persistence, and scaling state logic across large applications. Choose these when context performance becomes a bottleneck or when you need middleware support for side effects.

Server Components and Actions

In frameworks like Next.js App Router, React Server Components (RSC) render on the server and cannot use hooks. Data is passed to RSCs via props from the server, while Server Actions allow mutations. Client components interoperate with server components by receiving serialized props. This architecture reduces client-side JavaScript bundle sizes but requires framework support. Do not attempt to use hooks inside Server Components; pass data via props instead.

Best Practices

  • Lift State Up: Keep state in the closest common ancestor rather than globally unless necessary.

  • Avoid Prop Drilling: Use Context or composition when props pass through more than two intermediate components.

  • Optimize Context: Split contexts by domain (e.g., AuthContext vs. ThemeContext) to prevent unrelated updates from triggering re-renders.

  • Type Safety: Use TypeScript interfaces for props and context values to catch errors during development.

  • Server Boundaries: Ensure Server Components receive only serializable data and delegate interactivity to Client Components.

10

In React development, how would you define a component that maintains internal state or data across renders?

A stateful component is a React component that holds and manages its own internal data through state, allowing it to change behavior or appearance dynamically when that state updates. This internal data persists between renders and triggers re-renders when modified through specific methods.

Read full answer

Applies toReact 19 with function components

Core Concept

State is local data stored within a component that affects what gets rendered. When state changes, React automatically re-renders the component to reflect the new values. State allows components to track information like user input, API responses, UI interactions, or any dynamic value that needs memory.

Implementation Approaches

Approach Description Current Status
Function components with hooks Uses useState or useReducer hooks ✅ Recommended (modern)
Class components Extends React.Component with this.state ⚠️ Legacy (deprecated)

Key Features

  • Internal Storage: State lives only within the component and isn’t shared globally

  • Immutable Updates: Never modify state directly; always use setter functions

  • Automatic Re-rendering: Changes trigger fresh renders with updated UI

  • Component-scoped: Each instance maintains independent state

Common Patterns

For simple scalar or object values, use the useState hook which returns the current value and a function to update it:

import { useState } from 'react';

function Counter() {
  // Declare state variable named 'count' initialized to 0
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

For complex state logic with multiple actions, useReducer provides a cleaner pattern:

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    default: return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}

Best Practices

  • Avoid direct mutation by never assigning to state variables (e.g., no count = 1)

  • Initialize state from props when the source of truth originates externally

  • Split large components into smaller ones to reduce complexity

  • Lift state up when multiple components need to share data

  • Consider context or external state management (Redux, Zustand) for deeply nested sharing

  • Use functional updates (setCount(prev => prev + 1)) when the new value depends on the previous state

Common Pitfalls

  • Forgetting dependencies in effects when state interacts with side effects

  • Using stale closures by capturing incorrect values in callbacks

  • Treating state as regular variables instead of reactive data that drives renders

  • Managing too much state in one component without proper organization

11

Describe the mechanism and usage pattern of the useState hook in React functional components

The useState Hook is a built-in React API that adds local state to function components. It returns a state value and a setter that schedules a re-render with a new value.

Read full answer

Applies toReact 19 with function components

How It Works

On the first render, React stores the initial state; later reads return the stored value. Calling the setter does not mutate the current state; it schedules an update and a re-render. In React 19, useState works only in Client Components and is unavailable in React Server Components. Updates may be batched, so reading state immediately after calling the setter can still return the previous value. The setter’s identity is stable, so it can be passed as a prop or listed in a useEffect dependency array without triggering extra renders.

Key Features

  • Initial state: Accepts a literal value or an initializer function, useState(() => init), for expensive computations that should run once.

  • Functional updates: setState(prev => ...) guarantees access to the latest previous state, which matters when several updates depend on it or occur in the same event.

  • Automatic batching: React 18 and later batch updates inside event handlers, promises, timeouts, and most native events.

  • Immutable updates: State should be treated as immutable; objects and arrays must be replaced with new references to trigger re-renders.

Usage Patterns

  • Use primitive state for counters, flags, and form inputs.

  • Avoid derived state: compute values from props or existing state during render instead of storing them separately.

  • Keep independent values in separate useState calls rather than one large object, so unrelated updates do not force unnecessary re-renders.

  • Reset state by calling the setter with the initial value or by changing the component’s key.

Code Example

import { useState } from 'react';

function Counter() {
  // Initial state is 0; the initializer function runs only on the first render
  const [count, setCount] = useState(0);

  // Functional update avoids stale closure values from earlier renders
  const increment = () => setCount(c => c + 1);

  return (
    <div>
      <p>Current: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

Best Practices

Pattern When to use Example
Lazy initialization Expensive initial value useState(() => computeExpensiveValue())
Functional update New state depends on previous state setCount(c => c + 1)
Separate state variables Independent values const [name, setName] = useState('');
Immutable update Objects or arrays setItems(prev => [...prev, newItem])

Common Pitfalls

  • Mutating state directly, for example state.push(item), which does not trigger a re-render.

  • Using stale state inside event handlers; prefer functional updates or refs when the latest value is required.

  • Treating state updates as synchronous and reading state immediately after calling the setter.

  • Packing unrelated state into one object, which complicates resets and can cause unnecessary re-renders.

  • Forgetting that useState itself has no dependency array; dependency issues arise only when state is read inside useEffect.

12

What is the standard approach for a child component to modify or set data stored in its parent component's state?

The standard approach is called lifting state up, where the shared state is moved to a common ancestor and callback functions are passed down as props to allow the child to trigger updates.

Read full answer

Applies toReact 19 with function components

Core Concept

React enforces a unidirectional data flow where state can only be modified by the component that owns it. When a child needs to update data displayed by the parent, you lift the state to their closest common ancestor. This component then manages the state and passes both the current value and a function to update it down to the child through props. The child invokes the callback when it needs to make changes.

How It Works

  1. Identify the shared state: Find which piece of data both components need to access or display

  2. Move state to common ancestor: Declare the state variable and its updater function in the parent component using the useState hook

  3. Pass callbacks down: Provide a callback prop—often a wrapper around the parent’s updater—so the child can request changes without owning the state; for example, the parent can pass onIncrement={() => setCount(prev => prev + 1)}

  4. Child invokes the function: The child calls the callback from an event handler, such as a button click, and the parent re-renders with the new state

Key Features

  • Unidirectional flow: Data always flows downward through props, while events flow upward through callbacks

  • Single source of truth: Only one component (the parent) owns and manages the state

  • Predictable behavior: State updates follow React’s normal reconciliation process

  • Scalable pattern: Works well even when multiple siblings need to communicate indirectly

Comparison Approaches

Approach Best For Limitations
Lifting state up Most cases, simple component trees Becomes cumbersome in deeply nested structures
Context API Sharing data across many levels Overkill for direct parent-child communication
State management libraries Large-scale applications Adds complexity for simple use cases

Common Pitfalls

  • Don’t mutate state directly: Always use the updater function returned by useState, never assign directly to the state variable

  • Prefer functional updates: Use the form setCount((prev) => prev + 1) rather than reading stale closure values when the update depends on the previous state

  • Avoid unnecessary re-renders: Pass stable callback functions by wrapping them in useCallback when the child is memoized with React.memo

  • Keep children pure: The child should only call the provided callback, not try to manage state itself

Best Practices

  • Keep components focused: Each component should have a clear responsibility for its own UI

  • Prefer composition over deep nesting: If state lifting becomes complex, consider restructuring your component hierarchy

  • Use Context for global state: When many components at different levels need access to the same data, the Context API provides a cleaner alternative

  • Document callback contracts: Clearly specify what parameters a callback expects and when it will be invoked

This pattern is fundamental to React development and is required understanding for any React developer role. While additional tools like Context or state management libraries exist, lifting state up remains the primary approach for parent-child communication in typical application structures.

13

How do you share state between sibling components in React by moving state to a common parent?

Lifting state up is a design pattern in React where you move shared state from individual child components to their closest common ancestor, enabling multiple components to access and manage the same data through props.

Read full answer

Applies toReact 19 with function components and hooks

How It Works

When two or more sibling components need to share mutable state, neither can directly communicate with each other. Instead, you elevate the state to their parent component. The parent then passes the state down via props and provides callback functions (also passed as props) that children can invoke to update that shared state.

This follows React’s unidirectional data flow principle, where information flows from parent to child through props, while events flow upward through callbacks.

Key Steps

  1. Identify the shared state: Determine which pieces of state are needed by multiple sibling components.

  2. Move state to the parent: Declare useState in the common ancestor instead of individual children.

  3. Pass state as props: Provide the state value to children through component props.

  4. Pass updater functions: Provide callback functions that trigger state updates within the parent.

Component Comparison

Before Lifting State After Lifting State
Child A manages its own state independently Parent owns the state
Child B manages its own state independently Sibling receives state as prop
No direct communication between siblings Both receive state from parent
Each has separate state instance Single source of truth

Advantages and Limitations

  • Single Source of Truth: All components reference the same state value, eliminating synchronization issues.

  • Bidirectional Communication: Children can both read and trigger updates to shared state.

  • Simplified Architecture: Keeps related state logic in one place rather than scattered across components.

  • Potential Overuse Risk: Not every piece of state needs lifting; only lift what is genuinely shared.

Code Example

import { useState } from 'react';

// Shared counter state lives in Parent
function Parent() {
    const [count, setCount] = useState(0); // Common state

    return (
        <>
            <CounterDisplay count={count} /> {/* Passes state */}
            <CounterUpdater 
                onIncrement={() => setCount(prev => prev + 1)} {/* Functional update */}
            />
        </>
    );
}

function CounterDisplay({ count }: { count: number }) {
    return <div>Count: {count}</div>;
}

function CounterUpdater({ onIncrement }: { onIncrement: () => void }) {
    return <button onClick={onIncrement}>Increment</button>;
}

In this example, both CounterDisplay and CounterUpdater need to access the same count value. By lifting it to Parent, they stay synchronized without direct communication.

Best Practices

  • Lift only when necessary: Don’t over-engineer by lifting state unnecessarily. If components don’t need to share state, keep them independent.

  • Avoid prop drilling at scale: When passing state through many intermediate layers, consider context or state management libraries like Redux or Zustand.

  • Use TypeScript for clarity: Define interfaces for props including state values and callbacks to catch errors during development.

  • Keep children pure: Children receiving lifted state should be presentational rather than managing internal state for that shared data.

  • Test state changes: Verify that updates propagate correctly across all consuming components.

Common Pitfalls

Forgetting to pass the updater function will leave children unable to modify the shared state. Also, use functional state updates (setCount(prev => prev + 1)) in callbacks to avoid stale closure captures that cause incorrect increments.

14

What are the circumstances that call for choosing Redux over Context API or vice versa when managing application state?

For most modern React applications, Context API handles simple to moderate state needs, while Redux excels at complex, global state management requiring middleware, predictable debugging, and performance optimization for frequent updates.

Read full answer

Applies toReact 19 with Redux Toolkit

Core Differences

  • Context API: Built-in React solution for prop drilling; good for theme settings, authentication status, language preferences, and low-frequency updates. No external dependency required.

  • Redux: External library (typically Redux Toolkit) providing time-travel debugging, developer tools, predictable state containers, and optimized re-rendering patterns for high-frequency changes.

When to Use Context API

  • Simple cross-component data sharing without intermediate props

  • Non-performance-critical global state like dark mode or UI theming

  • Small to medium applications where setup complexity matters more than features

  • State that doesn’t change frequently across many components

  • Component-specific concerns like modal open/close or form control states within subtrees

When to Use Redux

Scenario Recommended Choice
Complex business logic requiring async operations Redux (with RTK Query or middleware)
Large-scale enterprise applications Redux
Multiple components subscribing to same state with frequent updates Redux + memoized selectors
Need for time-travel debugging or state replay Redux DevTools
Server data caching with deduplication RTK Query
Complex undo/redo functionality Redux (replay capability)
Multiple developers maintaining shared state Redux (structured action/reducer patterns)
State normalization needed Redux (normalized store structure)

Performance Characteristics

  • Context: Triggers re-renders in all consuming components when context value changes. Split contexts into multiple providers to limit unnecessary re-renders.

  • Redux: With proper memoized selectors (using Reselect or similar), only update when selected slice changes. The Redux Provider compares previous and next values to prevent redundant renders.

Modern Best Practices

  1. Start with Context: Prefer built-in solutions first unless you have proven scaling needs

  2. Use Redux Toolkit: If choosing Redux, use @reduxjs/toolkit (not legacy Redux); includes createSlice, configureStore, and RTK Query

  3. Hybrid Approach: Combine both—use Context for local component trees and Redux for cross-application global state

  4. Server Data: For fetching and caching remote data, prefer TanStack Query or RTK Query over manual state management

  5. Avoid Overengineering: Don’t use Redux for simple form state or transient UI flags

  6. Memoize Selectors: Use Reselect or useMemo in selectors to ensure Redux subscriptions stay efficient

Common Pitfalls

  • Premature Optimization: Adding Redux before needing it increases boilerplate unnecessarily

  • Ignoring Re-render Issues: Context triggers unnecessary re-renders if not split into multiple contexts

  • Overusing Global State: Keep state as local as possible; don’t put everything in global stores

  • Missing Selector Memoization: Without memoized selectors, Redux can also cause excessive re-renders even though Redux itself performs some optimization

Migration Path

If starting with Context and later needing Redux: 1. Identify patterns (frequent updates, complex async logic) 2. Extract those slices to Redux Toolkit 3. Keep simpler Context usage for independent concerns

Both tools serve different scales of complexity correctly when applied appropriately.

15

What are the specific lifecycle methods found in React class components and at which stages of the component lifecycle are they executed?

React class component lifecycle methods are legacy execution hooks invoked during mounting, updating, unmounting, and error handling; in React 19 they are superseded by Hooks such as useEffect, but knowing them is necessary for maintaining older codebases. New development should use function components with Hooks.

Read full answer

Applies toReact class components (legacy), React 19

Legacy Status and Modern Replacement

  • Class components are legacy in React 19; the React team recommends function components with Hooks for all new logic.

  • Common mappings: componentDidMount corresponds to useEffect(..., []); componentDidUpdate corresponds to useEffect(..., [deps]); componentWillUnmount corresponds to the cleanup function returned by useEffect.

  • render is required during each phase but should remain pure and free of side effects.

Mounting Phase

Runs when an instance is created and inserted into the DOM: - constructor: Initializes state and binds event handlers; must call super(props). - static getDerivedStateFromProps: Rarely used; returns an object to update state or null based on props, before rendering. - render: Returns JSX; must be pure and free of side effects. - componentDidMount: Runs once after the first render; used for data fetching, subscriptions, or DOM initialization.

Updating Phase

Runs when props or state change and a re-render occurs: - static getDerivedStateFromProps: Invoked again before every render when derived state is needed. - shouldComponentUpdate: Returns a boolean to control re-rendering; default is true, and React.PureComponent provides a shallow comparison. - render: Re-executes to produce the new output. - getSnapshotBeforeUpdate: Captures DOM information such as scroll position immediately before changes are applied; its return value is passed to componentDidUpdate. - componentDidUpdate: Runs after updates; receives previous props, state, and snapshot, and should compare them to avoid infinite loops.

Unmounting and Error Handling

  • componentWillUnmount: Runs before removal from the DOM; clean up subscriptions, timers, and network requests here to prevent memory leaks.

  • static getDerivedStateFromError: Renders a fallback UI when a child component throws during rendering.

  • componentDidCatch: Logs error details and performs side effects after an error is caught; receives the error and an info object.

Deprecated Unsafe Methods

These legacy methods are deprecated because they enable unsafe patterns and should never be used: - componentWillMount: Ran before the initial render; replaced by constructor or componentDidMount. - componentWillReceiveProps: Ran before receiving new props; replaced by static getDerivedStateFromProps or componentDidUpdate. - componentWillUpdate: Ran before an update; replaced by getSnapshotBeforeUpdate or componentDidUpdate.

Best Practices

  • Avoid writing new class components; use Vite or a framework such as Next.js with function components and Hooks.

  • When migrating legacy code, move side effects from componentDidMount and componentDidUpdate into useEffect, and move cleanup logic into its cleanup function.

  • Do not mix class lifecycle methods with Hooks in the same component; choose one paradigm.

  • Treat deprecated unsafe methods as forbidden, even if they still execute in React 19.

16

Can you explain the mechanism behind hooks in React and how they enable state management in function components?

Hooks are special functions that let you connect function components to React features like state management and side effects without using class components.

Read full answer

Applies toReact 19 with function components

How Hooks Work

React maintains a linked list of hook nodes for each component instance (the fiber’s memoizedState). During render, hooks are called sequentially and each call advances a pointer to the next node. That node stores the current state, a queue of pending updates, and a link to the next hook. Because the call order is identical on every render, React can reliably associate each hook with its persisted state. This design replaces class instance fields, but it does not eliminate closure pitfalls: a stale closure can still capture an outdated value if a dependency is omitted from an effect.

The Rules of Hooks

  • Call Hooks at Top Level: Never invoke hooks inside loops, conditions, or nested functions.

  • Only Call from React Functions: Use hooks exclusively in React function components or custom hooks.

  • React Version Compliance: Hooks require React 16.8+; earlier versions rely on class components.

Violating these rules breaks the ordering React depends on and produces runtime errors.

Built-in Hooks Overview

Hook Purpose Returns
useState Manage local component state [state, setState] tuple
useEffect Handle side effects (fetching, subscriptions, DOM updates) Optional cleanup function
useContext Subscribe to context changes Current context value
useReducer Complex state logic alternative to useState [state, dispatch] tuple
useRef Persist mutable values without triggering re-renders Mutable ref object ({current: T})
useMemo Memoize expensive calculations Cached computed value
useCallback Memoize callback functions Cached function reference

When Effects Are Needed

  • Needed: data fetching, integrating with external APIs, manual DOM manipulation, subscriptions, timers.

  • Not Needed: deriving values from props or state—compute those directly during render instead.

If you do use useEffect, always list every reactive value the effect reads to avoid stale closures or infinite loops.

Best Practices and Common Pitfalls

  • Dependency Array: Provide every reactive dependency. Missing entries cause stale closures; extra entries trigger unnecessary re-execution.

  • Derived Values: Calculate derived values during render rather than storing them in state and syncing them with an effect.

  • Custom Hooks: Extract reusable logic into custom hooks, and obey the rules of hooks inside them.

  • State Updates: Always use the setter returned by useState; never mutate state directly.

  • Cleanup Handlers: Return a cleanup function from effects to cancel subscriptions, timers, or in-flight requests.

  • Server Components: In React 19, prefer Server Components and Server Actions (through a framework such as Next.js) for data fetching; keep useEffect for client-only logic.

Class components remain legacy as of React 19; new development should prioritize function components with hooks.

17

What is the standard pattern for performing data fetching within React function components using hooks?

The standard approach combines useState to manage loading and result states with useEffect to trigger the network request when the component mounts or specific dependencies change. React does not include a built-in data fetching hook for client components, so developers must orchestrate the lifecycle manually or integrate third-party libraries designed for server state management.

Read full answer

Applies toReact 19

Core Fetching Pattern

  • useState: Initializes variables for data, loading, and error to drive conditional rendering.

  • useEffect: Encapsulates the side effect (the HTTP request) so it runs after the initial render.

  • Dependency Array: Determines when the effect re-runs; an empty array [] executes only once on mount, while including props or state variables triggers refetches when they change.

Managing Lifecycle and Cleanup

  • Cancellation: Use AbortController to cancel pending requests if the component unmounts or the dependency changes before the request completes.

  • State Safety: Ensure state updates only occur if the component is still mounted to prevent memory leaks and React warnings about updating unmounted components.

  • Race Conditions: When dependencies change rapidly, ignore stale responses so outdated data does not overwrite newer results.

Recommended Libraries

  • TanStack Query: The industry standard for managing server state, handling caching, retries, background refetching, and deduplication automatically.

  • SWR: An alternative library offering similar features with a slightly different API philosophy focused on stale-while-revalidate.

  • Framework Solutions: In Next.js App Router, prefer Server Components for initial data load to reduce client-side JavaScript and improve SEO, using hooks only for client-side interactivity.

Code Example

import { useState, useEffect } from 'react';

interface User {
  id: number;
  name: string;
}

function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`, {
          signal: controller.signal,
        });
        if (!response.ok) throw new Error('Failed to fetch');
        const data = await response.json();
        setUser(data);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err instanceof Error ? err.message : 'Unknown error');
        }
      } finally {
        setLoading(false);
      }
    };

    fetchUser();

    return () => controller.abort();
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user?.name}</div>;
}

Best Practices and Common Pitfalls

  • Dependency Integrity: Always include reactive values used inside the effect in the dependency array to avoid stale closures, but ensure they are stable (e.g., memoize objects) to prevent infinite loops.

  • Separation of Concerns: Do not mix data fetching logic directly inside components for complex apps; extract custom hooks like useUser for reusability and testing.

  • Server vs Client: Avoid fetching data on the client that is required for the initial render if server-side rendering is available, as this delays content visibility.

  • Error Boundaries: Wrap components in Error Boundaries to catch rendering errors that might stem from failed data operations, complementing the local error state.

18

What are the essential guidelines that govern the usage of hooks in React function components?

React hooks are governed by the Rules of Hooks, which require hooks to be called unconditionally at the top level of function components or custom hooks and only from those functions, so React can preserve state and track hook order across renders.

Read full answer

Applies toReact 19 with function components and hooks

The Core Rules

  • Call hooks at the top level: Hooks must not be called inside loops, conditions, or nested functions. They must also be called before any early return so the order stays identical between renders.

  • Call hooks only from React functions: Invoke hooks from function components or from custom hooks. Calling them from ordinary JavaScript functions or from legacy class components breaks React’s internal tracking.

  • Name custom hooks with use: Functions that call other hooks should be named useSomething. This convention lets linting tools identify them as custom hooks and apply the rules correctly.

Effect Dependencies and Cleanup

Side-effect hooks such as useEffect, useCallback, and useMemo rely on dependency arrays to decide when to rerun or recompute. - Exhaustive dependencies: Every reactive value read inside the effect body—props, state, or derived values—must appear in the dependency array. - Stable references: Objects or functions created in the component body change on every render. Move them inside the effect, or stabilize them with useCallback or useMemo. - Cleanup: Return a cleanup function from useEffect to cancel subscriptions, timers, or in-flight requests before the component unmounts or the effect reruns. - When an effect is not needed: If logic only needs to respond to a user event or can be computed during render, do not use useEffect.

Linting and Tooling

Static tooling is the main way teams enforce hook rules. - eslint-plugin-react-hooks: Provides the rules-of-hooks and exhaustive-deps rules that detect violations during development. - Configuration: In React projects using Vite or Next.js, the plugin is commonly included, but you should verify that it is enabled in the ESLint configuration rather than assuming it is active by default. - Ignoring warnings: Suppressing dependency warnings with comments should be rare and accompanied by a clear justification, because ignored warnings often cause stale closures.

Comparison of Dependency Patterns

Scenario Correct approach Incorrect approach
Fetching data when a prop changes useEffect(() => { ... }, [userId]) Omitting userId from the dependency array
Expensive calculation useMemo(() => compute(items), [items]) Calling compute(items) directly in the render body
Stable event handler useCallback(() => { ... }, []) Creating a new function on every render

Common Pitfalls and Legacy Patterns

  • Class components: Hooks cannot be used in class components, which are legacy. Convert them to function components before introducing hooks.

  • Conditional hook calls: Placing a hook inside an if block shifts the hook index for later hooks, causing React to mismatch state between renders.

  • Stale closures: Leaving updated state or props out of a dependency array means the effect captures an older value instead of the current one.

  • Missing cleanup: Failing to return a cleanup function can leave subscriptions or timers running after unmount, causing memory leaks or state updates on unmounted components.

19

Explain the mechanism and purpose of passing data between components using props in React applications

Props (short for properties) are the primary mechanism for passing data from a parent component to its children in React, making components reusable and enforcing a predictable unidirectional data flow.

Read full answer

Applies toReact 19 with function components

What Are Props

Props are read-only inputs supplied to a component when it is rendered. A parent component decides the values; the child receives them as a plain object and uses them to decide what to render. Because props are immutable, a child cannot change them, which keeps rendering deterministic.

How Props Work

  1. Parent defines values: In JSX, the parent writes <Child name="Alice" age={30} />. Each attribute becomes a prop.

  2. React passes the props object: React calls the child function with a single props object containing those values.

  3. Child reads the values: The child can destructure or read props.name, but only for rendering or computing derived values.

This flow is strictly top-down. If a child needs to influence a parent, it does so by calling a function passed as a prop, not by mutating the prop itself.

Types of Props

  • Primitives: strings, numbers, booleans, null, undefined.

  • Objects and arrays: structured data such as a user record or list of items.

  • Functions: callback handlers that let a child notify a parent of an event.

  • JSX elements: passed through the special children prop or any custom prop, enabling composition.

  • React elements and components: a parent can pass a component type for the child to render.

Props Versus State

Aspect Props State
Source Supplied by a parent Managed internally by the component
Mutability Read-only Can be updated with a state setter
Purpose Configure a component instance Track data that changes over time
Update trigger Parent re-renders with new values useState setter

Use state for form inputs, toggles, timers, or any value that must change in response to user interaction. Use props to configure how a component behaves for a particular use case.

Best Practices

  • Name props with camelCase: userName rather than user_name, matching JavaScript conventions and DOM attribute norms.

  • Treat props as immutable: Never assign to a prop or mutate a nested object inside it; create new values instead.

  • Validate props: Use TypeScript types/interfaces or PropTypes to catch misspelled or missing props during development.

  • Destructure when it improves clarity: Destructuring in the parameter list is common, but direct props.value is also valid.

  • Group related props: If a component receives many props, pass a single object to reduce coupling.

  • Use children for layout flexibility: It allows nested content without naming every slot.

  • Avoid deep prop drilling: When many intermediate layers receive props only to pass them down, consider Context or a state-management library.

Common Pitfalls

  • No prop validation: Typos in prop names silently produce undefined until runtime.

  • Mutating props: Changing a prop does not trigger a re-render and can corrupt shared data.

  • Confusing props with state: Data that changes locally belongs in state, not in a prop.

  • Prop drilling: Passing data through unrelated components makes refactoring harder.

  • Passing unstable function references: Inline functions created on every render can cause unnecessary child re-renders; memoize them when needed.

In React 19, function components with hooks are the standard; class components are legacy. The props mechanism is the same in both, but function components read props from their parameter.

20

Explain what prop drilling is in React and describe methods to prevent or solve this issue

Prop drilling occurs when data needs to pass through multiple intermediate components that only forward the props without using them, eventually reaching a deeply nested child component. This creates tight coupling and makes refactoring difficult since any change requires updating every intermediary component.

Read full answer

Applies toReact 19 with function components and hooks

Why Prop Drilling Happens

React’s one-way data flow means parent components pass data down to children via props. When a grandchild or great-grandchild needs data from the top-level parent, every component in between must accept and re-pass those props even though they never use them themselves. This happens because each component can only receive data from its immediate parent.

Solutions to Avoid Prop Drilling

Several approaches help eliminate prop drilling, each suited for different scenarios:

Approach Best For Drawbacks
Context API Global or shared data across many components Can cause unnecessary re-renders, harder to track data flow
Component Composition Simple UI structure passing JSX Limited to passing elements, not complex data logic
Custom Hooks Sharing logic between components Doesn’t solve cross-component state sharing directly
State Management Libraries Complex, frequently changing global state Adds boilerplate and learning curve

Using Context API

The Context API allows you to define a data context that any component can access without explicitly passing props through the entire tree. You create a context provider at a high level and consume it where needed.

Using Component Composition

Instead of passing data through props, you can pass entire components or use children as a special prop. This lets child components render content passed from parents without requiring prop chains.

Using State Management Libraries

For complex applications, libraries like Zustand, Redux Toolkit, or Jotai manage global state externally. Components subscribe only to the parts of state they actually use, avoiding deep prop chains entirely.

Best Practices and Common Pitfalls

  1. Avoid overusing Context: Only use Context for data accessed by many components or truly global values like themes, authentication, or language settings.

  2. Prefer component composition first: If only two components need to share data, lifting state up or using composition may be simpler than creating a new Context.

  3. Minimize re-renders: Split contexts when possible so that changes to unrelated data don’t trigger cascading re-renders across consumers.

  4. Memoize context values: Wrap the value passed to a Context Provider in useMemo so that consumers only re-render when the data they actually use changes.

  5. Don’t mix patterns: Choose one approach for a given type of data rather than combining Context and Redux for the same state.

When building modern React applications, understanding these patterns helps maintain clean architecture while minimizing unnecessary component restructuring during future development iterations.

21

Explain the purpose of React Context and when you should use it.

React Context provides a way to share data across the component tree without passing props through every level manually. It solves prop drilling by creating a centralized store that any component can access.

Read full answer

Applies toReact 19 with function components and hooks

How It Works

Context works through two main components: the createContext hook and consuming via Provider or hooks.

  • createContext(defaultValue): Creates a Context object that holds shared data

  • Provider: Wraps components that need access to the context value

  • Consumer pattern: Use context.Provider.value to pass data down, or use the useContext() hook to consume

When You Need Context

Use Context for data that multiple levels of components need without intermediate prop passing:

  • Authentication state (user info, login status)

  • Theme preferences (dark mode, light mode)

  • Language/localization settings

  • Global error handling

  • UI component configuration (modal z-index, notification system)

When NOT to Use Context

Avoid Context for frequently changing data as it causes unnecessary re-renders:

  • Real-time updates like mouse position

  • Form inputs being edited

  • Counter values updated by many users simultaneously

Scenario Recommended Approach
App-wide theme Context
User auth state Context + Zustand/Redux Toolkit
Form data Component state or React Query
Local component interaction useState with props

State Management Integration

For complex applications, combine Context with modern patterns:

  • Create an application layer where Context providers wrap your app

  • Use custom hooks that call useContext internally for cleaner code

  • Consider dedicated state management libraries (Zustand, Redux Toolkit) for global state with frequent updates

Best Practices

  • Keep context values stable to minimize re-renders by memoizing context values

  • Split contexts logically instead of one big context with everything

  • Document what each context provides clearly

  • Avoid putting transient state (like form inputs) in Context

Common Pitfalls

  1. Re-creating context values on every render causes child components to re-render unnecessarily

  2. Using Context for performance-critical paths introduces overhead

  3. Forgetting that Context subscribes all consumers when the value changes

22

What is the approach for using render props in React component design?

Render props is a design pattern in React where a component receives a function as a prop that returns JSX, enabling reusable logic across multiple components. In React 19 with modern best practices, this pattern has been largely replaced by custom hooks, composition, and higher-order components, though it remains valid for certain third-party library integrations and maintaining older codebases.

Read full answer

Applies toReact 19

How It Works

A render prop works by having a component accept a function prop that gets invoked during render, passing data or methods that determine what content appears in the component’s output. The parent component defines both the logic and the UI structure, while the child component acts as an orchestrator that calls the render function with relevant information.

Key Characteristics

  • The parent component passes a function to a child component via a prop

  • The child invokes this function with needed data, state, or methods

  • The returned JSX renders as part of the child component’s output

  • Enables logic reuse without duplicating business logic across components

Modern Alternatives

Pattern Best For Notes
Custom Hooks Logic reuse Most common replacement since React 16.8; handles state and side effects cleanly
Composition Layout structure Pass children via props.children or dedicated slot props like children, input, content
Context API Global state Avoids prop drilling for shared state across deeply nested component trees
Server Components/Actions Server-side operations Require frameworks like Next.js App Router; reduce client-side logic needs

Advantages and Limitations

  • Advantages: Simple mental model, explicit data flow between components, no hoisting or wrapper dependencies required

  • Limitations: Can create deeply nested callback chains that are harder to trace, more vertical whitespace than hooks-based approaches, requires explicit function signatures

When to Use Render Props

Consider this pattern primarily when: 1. Maintaining legacy codebases already using render props 2. Working with third-party libraries that depend on this pattern 3. The consuming logic truly varies per component in ways hooks cannot elegantly express 4. Building reusable components designed around function-as-prop interfaces

Best Practices

  • Prefer custom hooks for most logic extraction scenarios in new code

  • If using render props, use named props consistently (render, children) rather than arbitrary names

  • Keep render functions short and focused on UI concerns rather than complex logic

  • Document the expected function signature clearly through JSDoc comments or TypeScript types

  • Consider whether composition or hooks would provide cleaner alternatives before implementing render props

For React 19 projects, prioritize hooks and composition over render props for new development. Only use render props when specifically required by external libraries or when maintaining existing code.

23

Can you explain the purpose and usage of the children prop in React components?

The children prop is an implicit property in React that captures any content nested between a component’s opening and closing tags, allowing developers to build composable UI structures without explicitly declaring every passed element in the component signature.

Read full answer

Applies toReact 19

How It Works

When JSX is compiled, content placed between <Component> and </Component> is passed as a prop named children. This prop is available inside the component function alongside other props. It can hold strings, numbers, React elements, arrays, fragments, or even functions (in the render prop pattern). React treats children specially during rendering, injecting it wherever the component references it in its return statement.

Common Use Cases

  • Layout Containers: Components like Page or Card define structural markup while leaving inner content flexible.

  • Modals: A Dialog component manages focus and backdrop logic while rendering arbitrary user content.

  • Lists: A List component might render <ul> tags while mapping over children or allowing direct <li> insertion.

  • Composition: It enables wrapping components around others without knowing their specific implementation details beforehand.

TypeScript Typing

When using TypeScript, annotate children with React.ReactNode. This type union covers strings, numbers, elements, fragments, and nullish values. Avoid restricting it to JSX.Element unless you strictly need a single element, as this causes errors when passing strings or arrays. In React 19, this typing convention remains the standard for function components.

Code Example

interface CardProps {
  title: string;
  children: React.ReactNode;
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-content">{children}</div>
    </div>
  );
}

// Usage
function App() {
  return (
    <Card title="Welcome">
      <p>This content is passed as children.</p>
    </Card>
  );
}

Best Practices

  • Destructure Props: Extract children in the function signature for readability rather than accessing props.children.

  • Conditional Rendering: Verify children exist if the layout breaks without them, using {children && children} or checking length for arrays.

  • Prefer Composition: Use children for UI composition rather than passing large configuration objects that dictate rendering logic.

  • Avoid Manipulation: Do not use React.cloneElement to inject props into children unless necessary; prefer Context or explicit props for data flow.

24

What makes performance tuning crucial when building applications with React?

Effective performance optimization is critical in React because it ensures smooth user interactions and prevents unnecessary re-renders that degrade application responsiveness as complexity increases. Without it, even well-structured components can become sluggish under heavy state updates or large data sets, leading to high bounce rates and poor accessibility.

Read full answer

Applies toReact 18 with function components and hooks

Impact on User Experience

Users perceive slow interfaces as broken. In React, performance issues often manifest as input lag, delayed transitions, or freezing during data updates. Optimizing rendering cycles ensures that the main thread remains available for user input, maintaining a consistent frames-per-second (FPS) rate. This is especially vital on mobile devices where CPU resources are constrained compared to desktop environments. Performance guidelines apply across modern browsers including Chrome, Safari, Firefox, and Edge.

Understanding Re-renders

React re-renders a component when its state or props change. By default, when a parent component re-renders, all its children re-render as well, even if their props remain unchanged. This cascading effect can become expensive in deep component trees. Identifying which components actually need to update is the core challenge of React performance tuning.

Optimization Strategies

  • React.memo: Memoizes functional components to prevent re-renders when props haven’t changed

  • useMemo: Caches expensive calculations between renders

  • useCallback: Memoizes functions to maintain referential equality

  • Component decomposition: Splitting large components into smaller, isolated units

Strategy Use Case Overhead
React.memo Preventing child re-renders Low; adds comparison cost
useMemo Expensive computed values Moderate; adds cache overhead
useCallback Function references passed down Low; minimal runtime cost

Scalability and Maintenance

An unoptimized application becomes increasingly difficult to maintain as it grows. Performance bottlenecks often hide architectural issues, such as excessive state lifting or prop drilling. Addressing optimization early encourages cleaner component boundaries and efficient state management patterns. This technical discipline prevents accumulation of tech debt that would otherwise require costly refactoring later in the project lifecycle.

Common Pitfalls

  • Over-memoization: Manually wrapping every component in React.memo before measuring performance can make code harder to read and provide negligible gains

  • Inline Objects: Creating new object or array literals in JSX props triggers child re-renders because referential equality fails

  • Large Lists: Rendering thousands of DOM nodes without virtualization causes significant memory and layout thrashing

  • Unnecessary effects: Running useEffect with broad dependency arrays causes excessive re-runs

Best Practices

  • Measure before optimizing using React Developer Tools to identify actual bottlenecks rather than guessing

  • Apply memoization only after profiling confirms specific render costs

  • Split state so unrelated updates do not trigger the same components

  • Use keys correctly in lists to help React identify which items have changed, added, or removed

  • Consider code splitting with React.lazy() for large applications to improve initial load time

25

Can you explain the purpose of React.memo and identify the specific scenarios where applying this optimization is appropriate?

React.memo is a higher-order component that prevents unnecessary re-renders of functional components by memoizing the output based on prop changes. It optimizes performance when a component renders frequently with identical props, though it should not be applied prematurely without profiling.

Read full answer

Applies toReact 19

What Is React.memo

React.memo wraps a functional component to cache its rendered output. When the parent component re-renders, React checks if the props passed to the memoized component have changed. If they are shallowly equal to the previous props, React skips rendering the component and reuses the last rendered result. This is the functional component equivalent of the legacy class-based React.PureComponent.

How It Works

  • Shallow Comparison: By default, React compares each property in the props object using ===. Primitive values compare by value, while objects and arrays compare by reference.

  • Custom Comparison: You can pass a second argument, a comparison function (prevProps, nextProps) => boolean, returning true if the props are equal and rendering should be skipped.

  • Context Sensitivity: React.memo only checks props. If the component consumes context via useContext, changes to that context will still trigger a re-render regardless of memoization.

When to Use It

  • Expensive Components: Use it when rendering logic is computationally heavy or involves complex DOM structures.

  • Frequent Parent Updates: Apply it when a child component receives stable props but its parent re-renders often due to unrelated state changes.

  • Pure Components: Ensure the component renders the same output for the same props without side effects during rendering.

Avoid using it on every component by default. Most applications do not need this optimization, and the overhead of comparison can sometimes outweigh the benefits. Always measure performance with React Developer Tools before optimizing.

Common Pitfalls

  • Inline Objects and Functions: Passing inline objects { style: { color: 'red' } } or inline functions () => {} creates new references on every render, breaking the shallow comparison and causing re-renders anyway.

  • Missing Dependencies: If you use a custom comparison function, ensure it accurately reflects all relevant prop changes to avoid stale UI.

  • State Inside Memoized Components: Local state inside the memoized component still updates normally; memoization only affects props-driven re-renders.

To fix inline function issues, pair React.memo with useCallback for functions and useMemo for objects passed as props.

26

What distinguishes PureComponent from the base Component class in React?

PureComponent is a React class component base that automatically implements a shallow props and state comparison in shouldComponentUpdate, while the base Component always re-renders on parent updates unless you manually implement that lifecycle method. Both are legacy class patterns, and modern React development favors function components with hooks.

Read full answer

Applies toReact 19 (class components legacy); `PureComponent` available since React 15.3, `Component` in all versions.

How They Differ

  • Component: Renders whenever its parent renders or its own state changes. It has no built-in update check, so you must write shouldComponentUpdate(nextProps, nextState) and return a boolean to prevent unnecessary renders.

  • PureComponent: Extends Component and supplies a built-in shouldComponentUpdate that shallowly compares current props and state against previous ones.

Feature Component PureComponent
Update check None by default Automatic shallow comparison
Boilerplate Manual shouldComponentUpdate required None
Best for Complex update conditions Stable props/state with immutable updates
Main risk Unnecessary renders if unoptimized Skipped renders if data is mutated in place

How Shallow Comparison Works

  • Primitive values such as strings, numbers, booleans, null, and undefined are compared by value.

  • Objects and arrays are compared by reference identity only.

  • Passing a new object or array literal on every render therefore counts as a change, while mutating an existing object in place does not trigger a re-render even when its contents change.

Why Class Components Are Legacy

  • React 19 still supports class components, but they are not recommended for new code.

  • They cannot use hooks, and they cannot be used as React Server Components or Server Actions, which require a framework that supports them (for example, Next.js App Router).

  • New projects should use Vite or frameworks such as Next.js or React Router instead of Create React App, which is deprecated.

  • For function components, the modern equivalent is React.memo, optionally with a custom comparison function; useMemo and useCallback help keep references stable.

Best Practices and Common Pitfalls

  • Treat props and state as immutable: create new arrays or objects instead of mutating existing ones, or PureComponent may skip needed renders.

  • Avoid new literals in render: inline arrow functions or object props generate fresh references every render and defeat shallow comparison.

  • Prefer React.memo for function components: it is the current counterpart to PureComponent.

  • Measure before optimizing: the comparison overhead can outweigh savings for components that rarely render or produce simple output.

  • Watch nested data: shallow comparison ignores nested changes, so normalize state or move comparisons closer to the source.

  • Verify rendering behavior: use React DevTools or automated tests to confirm that optimizations do not block legitimate updates.

27

What is the reconciliation process in React and how does it determine which parts of the DOM need updating?

Reconciliation is the algorithm React uses to compare the new Virtual DOM against the previous snapshot and calculate the minimal set of changes needed to update the actual browser DOM. This process ensures that UI updates are efficient and performant by avoiding unnecessary direct manipulations of the real DOM.

Read full answer

Applies toReact 19

How the Diffing Algorithm Works

When state or props change, React creates a new tree of React elements. The reconciler compares this new tree with the previous render tree using a heuristic O(n) algorithm. If the root element types differ, such as changing a div to a span, React tears down the entire subtree and rebuilds it. If types match, it updates only the changed attributes. For component instances, React preserves the internal state and updates props, allowing DOM nodes to persist across renders when possible. React performs this comparison in two phases: the render phase creates the work tree, and the commit phase applies changes to the DOM.

Importance of Keys in Lists

When rendering arrays of elements, the key prop helps React identify which items have changed, been added, or been removed. Keys should be stable and unique among siblings, typically derived from data IDs rather than array indices. Using indices as keys can cause state corruption or inefficient updates if the list order changes, because React may reuse DOM nodes incorrectly based on position rather than identity. In React 19, proper key usage remains critical for preserving component state during list reordering or filtering operations.

Concurrent Rendering Behavior

In React 19, reconciliation operates within the concurrent renderer, allowing the engine to pause, abort, or restart work to keep the UI responsive. This means reconciliation work is not necessarily synchronous; high-priority updates like user input can interrupt lower-priority background updates. Developers do not control this directly but must ensure effects and renders are pure to avoid visual inconsistencies during interruptions. This architecture enables features like transitions and selective hydration without blocking the main thread for extended periods.

Best Practices and Pitfalls

Avoid mutating state directly, as reconciliation relies on reference changes to detect updates. Use React.memo to prevent unnecessary re-renders of child components when parent reconciliation occurs. Note that the React Compiler can also automate this optimization in supported configurations. Never use random numbers or Math.random() for keys, as this forces full remounts and loses component state. Remember that reconciliation happens in memory; excessive complexity in render functions still blocks the main thread despite concurrent capabilities. Always profile with React Developer Tools to identify unnecessary renders caused by parent-child reconciliation chains.

28

What strategies can you use to optimize render performance and avoid triggering unwanted component updates in React?

You can optimize render performance and avoid unwanted updates in React by minimizing re-renders with React.memo, stabilizing expensive values and functions with useMemo and useCallback, writing correct effect dependencies, and deferring non-urgent UI with transitions.

Read full answer

Applies toReact 19 with function components

Why Unnecessary Re-renders Happen

When a parent component re-renders, its children re-render by default because React reconciles the tree from the top down. React.memo and similar optimizations compare props shallowly with Object.is, so new object or function references created during render are treated as changed props even when their contents are identical. This is why inline literals, objects, and functions in JSX often trigger child updates.

Key Optimization Techniques

Component Memoization

  • React.memo: Wraps a functional component and skips re-rendering when props are shallowly equal. Provide a custom comparison function as the second argument when you need deeper equality checks.

  • When to use it: Apply it to components that render often, receive stable props, or contain expensive DOM work. Do not apply it to every component by default.

Stabilizing Values and Functions

  • useMemo: Caches the result of an expensive calculation and recomputes only when its dependency array changes. Use it for derived data such as filtered lists or formatted output.

  • useCallback: Caches a function identity so that a stable reference is passed to memoized children. It is essentially useMemo for functions.

  • Dependency correctness: Both hooks require complete dependency arrays. Missing dependencies cause stale closures; extra dependencies cause unnecessary recalculations.

Dependency Management in useEffect

  • Include all reactive values: Every variable from props, state, or scope that the effect reads should appear in the dependency array.

  • Empty array []: Use only for logic that must run once after mount, such as initial subscriptions or one-time setup.

  • No array: Omitting the array runs the effect after every render. This is rarely correct and should not be used as a manual-control mechanism.

  • When an effect is not needed: Do not use useEffect for event handlers, derived values, or state that can be computed directly during render. Use computation or refs instead.

Deferring Non-Urgent Updates

  • useTransition / startTransition: Marks state updates as non-urgent so React can keep the interface responsive while heavy trees re-render.

  • useDeferredValue: Defers re-rendering of a heavy subtree until the browser is idle, while showing the previous value temporarily.

  • use: In React 19, use can read promises and resources inside components, allowing data to be integrated into the render pipeline without manual loading state.

Technique Best For Cost
React.memo Preventing child re-renders Shallow comparison overhead
useMemo Expensive derived values Memory for cached value
useCallback Stable function props Closure retention
useTransition Keeping UI responsive during heavy updates Slightly delayed rendering
useDeferredValue Deferring expensive subtrees Temporary stale UI

Common Pitfalls

  • Inline objects and functions: Creating {} or () => {} directly in JSX generates new references each render and defeats memoization. Extract them and stabilize with useMemo or useCallback.

  • Missing dependencies: Excluding values from dependency arrays creates stale closures and subtle bugs. Treat exhaustive dependencies as the default.

  • Over-memoization: Adding useMemo and useCallback everywhere increases complexity and memory use without measurable benefit.

  • Memoizing primitives: Wrapping strings, numbers, or booleans in useMemo provides no benefit.

Best Practices

  1. Profile first: Use React DevTools Profiler to identify actual bottlenecks before optimizing.

  2. Optimize measured hotspots: Apply memoization only where rendering is provably expensive or frequent.

  3. Colocate state: Keep state as close as possible to the components that need it; lifting state too high widens the re-render boundary.

  4. Prefer composition: Pass components as children or use render props so unaffected parts of the tree stay stable.

  5. Use keys correctly: Stable keys help React preserve component state and avoid unnecessary remounts.

  6. Use transitions for heavy UI: Wrap non-critical state updates in startTransition to avoid blocking the main thread.

29

What methods are available for styling user interfaces in modern React development?

Styling in React is not enforced by the library itself, allowing developers to choose based on project needs, though CSS Modules and utility-first frameworks are currently preferred for maintainability. The approach often depends on whether the project uses client-side rendering or React Server Components within a framework like Next.js.

Read full answer

Applies toReact 19 with Vite or Next.js

Core Styling Strategies

React offers no built-in styling system, so the ecosystem provides several options. The main categories include plain CSS, CSS Modules, utility-first CSS, and CSS-in-JS. Each balances scope isolation, developer experience, and runtime performance differently. Selecting one early ensures consistency across the component tree and reduces technical debt during scaling. Understanding the trade-offs helps in maintaining long-term code health.

Comparison of Styling Approaches

Approach Scope Dynamic Styles Runtime Overhead SSR Compatibility
Plain CSS Global Limited None Yes
CSS Modules Local (scoped) Limited None Yes
Utility-first (Tailwind) Pre-defined classes Via props Build-time only Yes
CSS-in-JS Runtime-generated Fully dynamic Runtime JS Requires library support

CSS Modules and Global Styles

CSS Modules scope class names locally by default, preventing collisions without extra configuration. In Vite, renaming a file to .module.css enables this behavior automatically. Global CSS is still useful for resets or typography but should be minimized to avoid unintended side effects across components.

Utility-First Frameworks

Tools like Tailwind CSS allow styling directly within JSX using utility classes. This reduces context switching between files and encourages design system consistency. It is particularly effective for rapid prototyping and teams that prefer keeping style logic close to the markup. Configuration is handled via a central file, allowing customization of the design token system. Tailwind processes styles at build time, making it fully compatible with Server Components.

CSS-in-JS Libraries

Libraries such as Styled Components or Emotion enable dynamic styles using JavaScript logic. They support theming and prop-based styling easily. However, they introduce runtime overhead (styles injected via JavaScript) and can complicate Server Component usage because most CSS-in-JS solutions were designed for client-side rendering only. If using Server Components in Next.js or similar frameworks, verify the library explicitly supports streaming and hydration requirements. For new projects, consider whether the dynamic styling benefits outweigh the additional complexity and bundle size.

Best Practices and Pitfalls

Avoid using inline styles for complex layouts as they lack pseudo-class support like :hover and media queries. When using Server Components, ensure your styling solution is compatible with server rendering to prevent hydration errors or style flickering. Always prefer documented tooling over custom solutions, and remember that Create React App is deprecated; use Vite or a full-stack framework for new projects to benefit from modern bundler features. Finally, test visual changes across breakpoints to ensure responsive behavior remains intact during refactoring. Adopting a linter plugin for CSS classes can catch unused styles early.

30

Define CSS-in-JS styling approach and describe methods to apply it within React applications?

CSS-in-JS is a styling methodology in which styles are written in JavaScript and scoped to components, so CSS is generated and injected at runtime instead of loaded from external stylesheets. It keeps presentation logic co-located with UI code and enables dynamic values through props, state, or theme.

Read full answer

Applies toReact 19 with function components and hooks, using styled-components v6 and @emotion/react v11

How It Works

  • Automatic scoping: Each component gets unique class names, eliminating global collisions.

  • Dynamic styling: Interpolations read props, state, or theme to change colors, spacing, or layout.

  • Colocation: Styles live next to the component that uses them, reducing context switching.

  • Theming: A theme object is consumed by styled components for consistent design tokens.

  • Server rendering: Libraries serialize critical CSS on the server when integrated with a framework such as Next.js App Router.

Implementation Methods

Approach Library / API Syntax style Main advantage
Inline style object React built-in style prop style={{ color: 'red' }} Zero dependencies
Styled component factory styled-components v6 Tagged template literals Full CSS syntax and nested selectors
CSS prop @emotion/react v11 css={css…} or styled() Fast runtime and small bundles
Utility composition Tailwind CSS with clsx/cn className={cn(...)} Predictable generated CSS, not pure CSS-in-JS

Note: Tailwind is a utility-first CSS framework; it is listed only as a common alternative, not a CSS-in-JS library.

Code Example

import styled from 'styled-components';
import { css } from '@emotion/react';

// styled-components v6: dynamic props and pseudo-selectors
const Button = styled.button`
  background: ${(props) => (props.primary ? '#007bff' : '#6c757d')};
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    opacity: 0.9;
  }
`;

// Emotion v11: css prop with conditional interpolation
function Card({ variant }) {
  const cardStyles = css`
    padding: 20px;
    margin: 16px 0;
    border-radius: 8px;
    ${variant === 'dark' ? 'background: #333; color: white;' : ''}
  `;

  return <div css={cardStyles}>Card Content</div>;
}

function App() {
  return (
    <>
      <Button>Default</Button>
      <Button primary>Primary</Button>
      <Card variant="dark" />
    </>
  );
}

Advantages and Limitations

Advantages: - Component encapsulation prevents style leaks. - Dynamic styles avoid manual className toggling. - TypeScript support catches invalid style properties at compile time. - Theme providers unify tokens across an application.

Limitations: - Runtime style injection adds JavaScript overhead compared with static CSS. - Styles are harder to grep because they are inside JavaScript. - Browser DevTools show generated class names rather than source files unless source maps are configured. - React Server Components cannot directly use most CSS-in-JS APIs; framework-specific integration is required.

Best Practices

  1. Define styled components outside render: Creating them inside a component body regenerates classes on every render.

  2. Memoize expensive styles: Wrap css blocks in useMemo or wrap components in React.memo; there is no public emotion.memo API.

  3. Prefer props over conditional className: Let the library handle variants.

  4. Configure SSR: Use Next.js App Router plugins for styled-components or an Emotion cache provider for server rendering.

  5. Avoid over-nesting: Deeply nested interpolations hurt readability and runtime performance.

  6. Use Vite or a framework: Create React App is deprecated; start new projects with Vite, Next.js, or React Router.

  7. Maintain accessibility: Ensure focus styles, contrast ratios, and aria attributes are preserved by generated CSS.

31

Explain the mechanism behind Styled-Components and how it enables component-styled JavaScript.

Styled-components is a runtime CSS-in-JS library that lets you write real CSS inside JavaScript using tagged template literals; it parses those styles, generates locally scoped class names, injects them into the DOM, and automatically binds them to React components so styling travels with component logic.

Read full answer

Applies toStyled-components v6 with React 19 (also React 18)

Core Mechanism

  • Tagged template literal: The call styled.button receives a CSS block such as color: red;; the library passes it to the stylis parser to produce real CSS rules.

  • Component factory: styled returns a React component constructor that wraps the target element or existing component and attaches the parsed style block.

  • Scoped class generation: At render time each instance receives a unique class name; in v6 the identifier is generated with React useId, so server and client produce the same class and hydration stays consistent.

  • Style injection: A stylesheet manager inserts the generated CSS into a <style> element in <head> (or a configured target) and caches rules so duplicates are not written to the DOM.

  • Prop interpolation: Expressions inside ${...} are functions called with the current props and theme during render; their return values are serialized into CSS, enabling dynamic styling without inline style objects.

  • Theme context: ThemeProvider places a theme object on React context; components read it via props.theme or the useTheme hook.

How It Enables Component-Styled JavaScript

  • Co-location: Styles live in the same file and often the same export as the component, so markup, behavior, and appearance move together.

  • JavaScript power: Styles can reference variables, props, theme values, and helper functions, removing the need for separate naming methodologies such as BEM.

  • Automatic class binding: The generated class is applied by the component itself, so developers never manually concatenate className strings.

  • Composition: Base styles are reused by wrapping an existing component, for example styled(Button) with a new CSS block; the css helper extracts reusable style fragments.

  • Global scope control: Ordinary components are scoped, while createGlobalStyle and keyframes handle document-wide rules and animations explicitly.

Server-Side Rendering and Hydration

  • On the server, ServerStyleSheet (or a framework integration such as Next.js) collects the styles rendered by the component tree into a string.

  • The collected CSS is emitted as <style> tags whose class names match the server-rendered HTML.

  • On the client, v6’s stable class-name generation means the first paint matches the React tree; without matching styles, React reports a hydration mismatch.

  • Custom Node servers must manually collect and serialize styles; framework App Router setups usually do this automatically.

Advantages and Limitations

Advantages Limitations
Scoped styles eliminate class-name collisions Runtime style injection adds client-side overhead
Dynamic props and theming are first-class Larger bundle size than static CSS files
Styles travel with components, improving portability SSR requires explicit style collection and ordering
No build step is required for basic usage Deeply nested interpolations can hurt render performance
Hot module replacement updates styles instantly Developers must learn the CSS-in-JS mental model

Best Practices

  • Extract shared fragments: Use the css helper for repeated style blocks instead of copying strings.

  • Prefer attrs: Set static props and DOM attributes with attrs rather than computing them inside CSS interpolations.

  • Keep interpolations cheap: Avoid heavy calculations in ${props => ...}; memoize derived values in the component instead.

  • Enable production plugins: Use the SWC or Babel plugin for dead-code elimination, minification, and faster compilation.

  • Limit global styles: Reserve createGlobalStyle for resets and fonts; keep component styles scoped.

  • Use a recent version: For React 19, use styled-components 6.1+, which supports React 19 and removes legacy warnings.

32

What techniques does React Router offer for supplying data to components rendered by specific routes?

React Router supplies data to route components through URL-based mechanisms—route parameters, query parameters, and navigation state—and, from v6.4 onward, route loaders that fetch data before a route renders.

Read full answer

Applies toReact Router v6.4+ (including React Router v7) with React 19

Route Parameters

  • Dynamic path segments declared with a colon (/projects/:projectId) identify a specific resource.

  • Read them with useParams, which returns plain string values; convert with Number, BigInt, or a parser before using them in logic or API calls.

  • Because they are part of the path, they are bookmarkable, shareable, and survive page refreshes.

  • Use them for resource identity, not for optional UI state.

Query Parameters

  • The search string (?status=open&page=2) holds optional view modifiers such as filters, sorting, or pagination.

  • useSearchParams returns a URLSearchParams instance and a setter; reading or updating them does not trigger a full navigation.

  • Values are strings and may be absent, so provide defaults and validate before sending them to APIs.

  • Ideal for state users may want to share or bookmark.

Navigation State

  • Pass an object via navigate(to, { state }) or <Link state={...}>; read it with useLocation().state.

  • It never appears in the URL, so it is useful for transient context such as “came from list” or a partially completed form.

  • It is lost on refresh, direct bookmarking, or cross-tab navigation, so never use it as the sole key for required data.

Route Loaders and Data APIs

  • A loader function attached to a route config runs before the route renders and can fetch from a server, database, or cache.

  • Components read the result with useLoaderData; errors surface through useRouteError and an errorElement.

  • Loaders require a data router, typically built with createBrowserRouter and rendered via RouterProvider, rather than the basic BrowserRouter/Routes setup alone.

  • For slow dependencies, defer streams promises and Await with Suspense renders fallbacks, keeping the UI responsive.

  • This is the preferred technique for server data because it co-locates fetching with the route and supports pending and error states.

Comparison and Selection

Technique Visible in URL Bookmarkable Survives refresh Typical use
Route parameters Yes Yes Yes Resource identity
Query parameters Yes Yes Yes Filters, sorting, pagination
Navigation state No No No Transient UI context
Loaders No, data only Indirectly Yes Server or cache data for the route

Best Practices and Pitfalls

  • Prefer loaders for data that must be fetched; reserve URL and state mechanisms for routing concerns.

  • Always parse and validate useParams and useSearchParams values; never assume numeric or boolean types.

  • Treat location.state as possibly null; guard access with optional chaining or a fallback.

  • Do not store tokens, passwords, or PII in URLs or navigation state; URLs are logged in browser history, proxies, and server logs.

  • When using useSearchParams inside useEffect, include the search params in the dependency array or derive values during render to avoid stale closures and infinite updates.

  • React Router v5 used render props and history.location.state; v6/v7 uses element props and hooks. Do not mix legacy patterns in React 19 projects.

33

Explain the concept of route guards and describe the approach to enforcing access restrictions on navigation paths within a React application.

Route guards are mechanisms that restrict access to specific views based on conditions like authentication status or user roles. Since React core does not handle routing, route guards are implemented by wrapping protected components in a custom component that conditionally renders a redirect using a library such as React Router.

Read full answer

Applies toReact 19 with React Router v6.4+

Concept and Purpose

Route guards prevent unauthorized users from accessing sensitive parts of an application. Common scenarios include requiring a valid session token before displaying a dashboard or restricting admin pages to users with specific permissions. Without guards, users could manually navigate to protected URLs and see content they should not access.

Implementation Pattern

In React 19, route guards are built as function components that evaluate the current user state. If the condition fails, the component renders a <Navigate> element to redirect the user. If the condition passes, it renders the intended child component or an <Outlet> for nested routes. This logic should reside in a reusable component rather than being duplicated across every protected page.

Integration with React Router

React Router v6 defines routes using the <Route> component with an element prop. The guard component wraps the protected element passed to this prop. When the router attempts to render the route, the guard executes first. If authentication is missing, the router renders the redirect instead of the protected UI, effectively blocking access before the component tree mounts.

Handling Asynchronous Authentication

Authentication checks often require asynchronous API calls to validate tokens. During this validation, the guard should render a loading indicator rather than redirecting immediately. Premature redirection can cause flickering or log users out incorrectly if the validation request is still in flight. A loading state ensures the user waits until the access decision is definitive.

Code Example

import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from './authContext';

function PrivateRoute() {
  const { user, isLoading } = useAuth();

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

  if (!user) {
    return <Navigate to="/login" replace />;
  }

  return <Outlet />;
}

In this example, PrivateRoute uses context to read auth state. It renders <Outlet /> to allow nested routes defined under it in the router configuration to display when authorized. The replace prop ensures the login page does not remain in the browser history stack.

Best Practices and Pitfalls

  • Avoid Infinite Loops: Ensure the redirect target (e.g., /login) is not itself protected by the same guard, which would create a redirect loop.

  • Server-Side Considerations: Client-side guards only protect the UI. API endpoints must independently verify authentication, as clients can be manipulated.

  • Dependency Management: If using hooks inside the guard, follow the Rules of Hooks by calling them unconditionally at the top level of the component.

  • Framework Support: For server-rendered applications, Next.js App Router provides built-in authentication patterns that may replace manual guard components.

35

How do you define and use the render prop pattern in React?

A render prop is a technique for sharing code between React components using a function-based prop whose value is itself a function that returns JSX. The component accepts a prop that is a function and calls it to determine what to render.

Read full answer

Applies toReact 16 through React 18 (superseded by hooks in React 19+)

Core Concept

Render props enable code reuse by passing a function as a regular prop that returns React elements. This allows a parent component to extract logic into a child component while deferring how to display content to the caller. The component handles the logic and passes data to the render prop function, which returns the UI.

How It Works

  • A component receives a function as a prop (commonly named render, children, or similar)

  • The component calls that function with data or state values

  • The function returns JSX, which the parent uses to compose its UI

The key principle is that the render prop component handles the logic while the caller decides what renders on screen based on runtime values.

Common Patterns

Pattern Description Use Case
render={function} Prop named render that returns JSX Classic render prop pattern
children={function} Function passed as <Component>{...}</Component> children More flexible syntax, same underlying mechanism
Named render props Multiple functions like renderData, renderError Complex components with multiple rendering needs

When Render Props Are Useful

  • Sharing logic between components without tight coupling

  • Creating reusable higher-order-like behavior without wrapping components

  • Components where dynamic rendering depends on runtime values

  • Building libraries that need maximum flexibility in output layouts

Modern Alternatives in React 19+

By React 19+, render props are largely superseded by:

  • Custom hooks: Extract logic into reusable functions that return values and handlers

  • React Context: Provide shared data without passing props explicitly through intermediate components

  • Compound components: Build component families that share internal state

Hooks provide better developer experience since they run naturally alongside your component lifecycle and integrate well with TypeScript. They also avoid the nesting complexity that deep render prop chains can create.

Best Practices and Limitations

  • Prefer custom hooks for React 19+ projects when extracting reusable logic

  • Avoid render props for simple data passing; regular props work better

  • Be explicit about prop names so other developers understand the component API

  • Don’t nest multiple levels deeply as it becomes harder to follow

  • Consider bundle size impact when choosing between render props and hooks

Use render props when building libraries or tools where maximum flexibility matters, but default to hooks for application code in modern React. For existing render prop implementations, migrate them to custom hooks gradually as refactoring opportunities arise.

37

How do controlled and uncontrolled components differ when managing input state in React?

In React, a controlled component renders form elements whose values are driven by React state, while an uncontrolled component stores its state in the DOM itself. The choice determines where the source of truth lives and how input values are read during validation or submission.

Read full answer

Applies toReact 19 with function components and hooks

38

Explain the purpose of the Formik library and describe how it is implemented to manage state and validation in React forms.

Formik is a popular third-party library designed to simplify form state management in React applications by handling values, errors, and submission logic declaratively. It reduces boilerplate code associated with tracking input changes, validation rules, and submission states compared to manual useState implementations.

Read full answer

Applies toFormik 2.x with React 19, Next.js App Router

Core Architecture

Formik operates by wrapping form components in a provider that stores the entire form state in a single object. Instead of managing individual useState hooks for each input field, developers define initialValues and let Formik track changes automatically. This centralized state approach ensures that validation errors and touched fields remain synchronized with input values throughout the component lifecycle.

Key Components and Hooks

  • <Formik> Component: The main provider that accepts initialValues, validate, and onSubmit props to configure form behavior.

  • useFormik Hook: A lower-level hook that returns handlers and state values, useful when custom markup is required without the wrapper component.

  • <Field> Component: Connects an input element to the form state by name, automatically wiring onChange, onBlur, and value.

  • <ErrorMessage> Component: Displays validation messages only when a specific field has been touched and contains an error.

Validation Strategies

Validation can be implemented synchronously via the validate prop or asynchronously using promises. For complex schemas, Formik integrates seamlessly with Yup, a schema builder for value parsing and validation. When using Yup, the validationSchema prop replaces the validate function, allowing declarative rule definitions such as required fields, email formats, and minimum string lengths.

Code Example

import { Formik, Form, Field, ErrorMessage } from 'formik';

const LoginForm = () => (
  // Define initial form values and their types
  <Formik
    initialValues={{ email: '', password: '' }}
    // Validate inputs against defined rules before submission
    validate={(values) => {
      const errors: Record<string, string> = {};
      if (!values.email) errors.email = 'Email is required';
      if (!values.password) errors.password = 'Password is required';
      return errors;
    }}
    // Handle submission and manage loading state
    onSubmit={(values, { setSubmitting }) => {
      // Simulate network request (replace with actual API call)
      setTimeout(() => {
        console.log(values);
        setSubmitting(false);
      }, 400);
    }}
  >
    <Form>
      <div>
        {/* Bind field 'name' to Formik state */}
        <Field type="email" name="email" placeholder="Email" />
        {/* Display error message for the field */}
        <ErrorMessage name="email" component="div" />
      </div>
      <div>
        {/* Bind field 'name' to Formik state */}
        <Field type="password" name="password" placeholder="Password" />
        {/* Display error message for the field */}
        <ErrorMessage name="password" component="div" />
      </div>
      <button type="submit">Login</button>
    </Form>
  </Formik>
);

Best Practices and React 19 Considerations

  • Use Native Features When Possible: React 19 introduces enhanced native form handling capabilities (e.g., useFormState with Server Actions in Next.js App Router). For simple forms, native React state or framework solutions may reduce bundle size.

  • Avoid Over-Engineering: Do not use Formik for trivial inputs; manual useState is often lighter and easier to debug for single-field forms.

  • Performance: Memoize heavy validation logic to prevent unnecessary re-renders during rapid typing.

  • Accessibility: Ensure <ErrorMessage> components are properly linked to inputs using aria-describedby for screen readers.

  • Deprecation Awareness: Create React App is deprecated; initialize new Formik projects using Vite or a framework like Next.js.

39

What advantages does integrating TypeScript into a React project provide?

Using TypeScript with React adds static type checking to JavaScript, catching errors during development rather than at runtime. It enhances component reliability, improves developer tooling through autocomplete, and simplifies large-scale refactoring by enforcing strict contracts between components and hooks.

Read full answer

Applies toReact 19 with TypeScript 5.x

Type Safety and Prop Validation

TypeScript allows you to define explicit shapes for component props and state. This prevents passing incorrect data types, such as sending a string where a number is expected. In React 19, function components accept typed props directly without needing the legacy React.FC wrapper, which simplifies generic handling. - Props Interface: Define an interface or type for props to ensure consistency across component usages. - Event Handlers: Types ensure event objects match the expected DOM element, for example React.ChangeEvent<HTMLInputElement>. - Children Handling: Modern React types children like any other prop, but explicitly typing children remains useful for layout components.

Enhanced Developer Experience

Integrated Development Environments (IDEs) leverage type information to provide intelligent code completion. This reduces the need to memorize component APIs or jump between files constantly. - Autocomplete: Suggests valid props and methods based on defined types. - Navigation: Allows jumping to type definitions to understand data structures quickly. - Inline Errors: Highlights mismatches immediately as you type, reducing debugging time later.

Refactoring Confidence

When renaming props or changing state structures, the TypeScript compiler identifies every location that requires updates. This is critical in large codebases where manual tracking is error-prone. - Safe Renames: Changing a prop name triggers errors at all usage sites. - API Changes: Updating a backend response type propagates errors to components consuming that data. - Dead Code Detection: Unused variables or unreachable code paths are often flagged.

Hook Type Inference

React hooks benefit significantly from TypeScript generics, reducing boilerplate while maintaining safety. The compiler infers types from initial values but allows explicit definition for complex states. - useState: Infers type from the initial value, for example useState(0) becomes number. - useReducer: Requires defining the state and action types to ensure dispatch payloads are valid. - Custom Hooks: Return types can be documented, making hooks reusable across projects without ambiguity.

Best Practices and Pitfalls

Adopting TypeScript effectively requires discipline to avoid weakening the type system. - Enable Strict Mode: Always set "strict": true in tsconfig.json to catch null and implicit any errors. - Avoid any: Use unknown or specific types instead of any to maintain type safety. - Framework Integration: For new projects, use Vite or Next.js instead of deprecated Create React App. - Server Components: Remember that React Server Components require a framework like Next.js and have different typing constraints than client components.

40

How do you define and apply TypeScript interfaces to type props and state in React function components?

In React development with TypeScript, you use interfaces to strictly define the structure of props and state within function components. This approach enables compile-time checking, preventing incorrect data types from being passed down the component tree. Interfaces are typically preferred for object shapes because they support declaration merging and provide clear contract definitions for component APIs.

Read full answer

Applies toReact 19 with TypeScript 5.x

Defining Props Interfaces

To type a component, declare an interface describing the expected properties and pass it as the function parameter type. Avoid using React.FC because it implicitly adds children and is considered legacy in modern TypeScript React projects. Export the interface if the component is part of a public library API so consumers can extend it. - Props Interface: Defines external input data shape. - Function Signature: function Component(props: Props). - Public API: Export interfaces for reusable component libraries.

Typing Component State

When using the useState hook, specify the type as the generic type parameter to guarantee the state variable adheres to the defined shape. You can use a type literal inline or define a named type. For complex state logic involving multiple sub-values, consider useReducer with action types instead. If the initial value may be null or undefined, use a union type to reflect the loading or uninitialized state accurately. - Generic Syntax: useState<UserData>(null) or useState<{name: string}>({name: ''}). - Complex State: useReducer<State, Action> for intricate logic. - Union Types: UserData | null handles uninitialized states safely.

Handling Children and Optional Fields

Components that wrap other content must explicitly type the children prop using ReactNode to accept strings, numbers, elements or arrays. Mark non-essential properties with a question mark to make them optional, allowing consumers to omit them without triggering type errors. Provide default values via destructuring to ensure runtime safety alongside compile-time safety. Use read-only modifiers on props in strict mode to prevent accidental mutation. - Children Type: children?: ReactNode. - Optional Props: title?: string. - Defaults: const { title = 'Default' } = props.

Code Example

import { useState, ReactNode } from 'react';

interface CardProps {
  /** Required title for the card */
  title: string;
  /** Optional click handler */
  onClick?: () => void;
  /** Content rendered inside the card */
  children?: ReactNode;
  /** Optional custom className */
  className?: string;
}

export function Card({ title, onClick, children, className = 'card' }: CardProps) {
  const [isExpanded, setIsExpanded] = useState<boolean>(false);

  const handleClick = () => {
    setIsExpanded((prev) => !prev);
    onClick?.();
  };

  return (
    <div className={className} onClick={handleClick}>
      <h2>{title}</h2>
      {isExpanded && children}
    </div>
  );
}

Best Practices and Common Pitfalls

Always define interfaces outside the component body to maintain stability across renders. Do not use any to bypass type checking—instead, refine the interface structure or use unknown with proper type guards when handling dynamic data. Ensure optional props are handled in the logic to prevent runtime undefined errors despite passing compile-time checks. When working with third-party components, prefer React.FC<TProps> only for backward compatibility, and always type ref objects with forwardRef<HTMLDivElement, CardProps>() when needed. - Interface Placement: Define outside components to maintain stability. - Type Safety: Prefer unknown over any for dynamic values; use type guards to narrow types. - Defaults: Always provide sensible defaults for optional props. - Event Handlers: Use optional chaining (onClick?.()) for optional callbacks.

41

What advantages do TypeScript generics provide when building reusable React components?

TypeScript generics enable type-safe, reusable React components by allowing components to work with various data types while maintaining compile-time type checking throughout your application.

Read full answer

Applies toTypeScript 5.x with React 19 and function components

How Generics Work in React Components

Generics allow you to define components with placeholder types (usually denoted with angle brackets like <T>) that get resolved when the component is used. This creates flexibility without sacrificing type safety. The generic type parameter becomes part of the component’s public interface, so consumers know exactly what types their props will have at compile time.

Key Benefits

  • Type Safety Across Component Hierarchy: Props, state, and events stay correctly typed from parent to child components

  • Reusable Logic Without Sacrificing Types: One component implementation works with any data structure

  • Better Editor Experience: IntelliSense, autocomplete, and refactoring work perfectly throughout your app

  • Fewer Runtime Errors: Type mismatches are caught during development rather than at runtime

  • Self-Documenting Code: Generic constraints express intent clearly to other developers

Common Use Cases

Component Pattern Without Generics With Generics
Form inputs value: any, manual validation value: string, type-preserving
API wrapper Generic fetch return Typed response based on endpoint
Context providers any context values Specific context shape
List components Manual prop validation Type-safe children mapping

When You Need Generics

Use generics when your component needs to handle different data types that shouldn’t be narrowed down to any:

  • Lists and collections: Display items of various types while preserving type information for rendering

  • Context providers: Share specific data shapes across components without type erosion

  • API utilities: Wrap fetch calls while maintaining response types matched to endpoints

  • Custom hooks: Return typed results that vary based on input parameters

Type Constraints and Defaults

You can add constraints to limit what types can be used:

Generic constraints restrict which types are allowed using extends:

  • T extends Identifiable allows only types with an id property

  • T extends object requires any non-primitive type

  • Multiple constraints can be combined: T extends TypeA \| TypeB

Default types apply when no argument is provided:

  • T = SomeBaseType sets a fallback when the caller omits the generic

  • This makes components progressively typed without breaking existing usage

Common Pitfalls and Best Practices

  • Avoid any: Don’t fall back to any just because typing is complex; properly type the generic

  • Explicit generic arguments: Let TypeScript infer when possible, but specify when inference fails

  • Don’t overuse: Not every component needs generics—only those that genuinely work with multiple types

  • Test edge cases: Ensure your generic handles null, undefined, and empty states appropriately

  • Document constraints: Use JSDoc to explain what types are valid for your generic parameters

  • Prefer union types first: Sometimes T extends TypeA \| TypeB is clearer than abstract constraints

Framework-Specific Considerations

  • Next.js App Router: Can combine generics with Server Actions for type-safe API responses, but Server Actions require a framework that supports them

  • React Query: Use generic hooks for typed query results and mutations

  • Form libraries: Most form libraries support generics for typed field management

Generics represent one of the most powerful TypeScript features for building maintainable, scalable React applications with strong type guarantees throughout the entire component tree.

42

What are the main reasons for implementing testing strategies in React applications?

Testing plays a critical role in React development by catching bugs early, ensuring components behave predictably, and providing confidence when making changes to your codebase. It helps prevent regressions, improves code quality, and makes refactoring safer for production applications.

Read full answer

Applies toReact 19 with function components and hooks

Preventing Regressions

When you modify existing code or add new features, tests act as a safety net that verifies nothing has broken unexpectedly. Without automated tests, manual regression checking becomes time-consuming and error-prone, especially as applications grow in complexity. Regression prevention ensures that fixes don’t introduce new issues elsewhere in the application.

Ensuring Component Behavior

React components are building blocks that handle state, props, and side effects. Tests verify that:

  • Props: Components render correctly with different input values

  • State: Internal state transitions occur as expected after user interactions

  • Side Effects: useEffect hooks trigger at the right times without unnecessary re-renders

  • Events: User actions like clicks and form submissions produce the correct responses

Without proper testing, subtle bugs in component logic can slip into production, causing unexpected behavior for users.

Types of Tests in React

Different test types serve different purposes in your workflow:

Test Type Focus Tools When to Use
Unit Tests Individual functions, hooks, utilities Jest, Vitest Always write for core logic
Component Tests Visual rendering, user interactions React Testing Library For each public-facing component
Integration Tests Multiple components working together Jest + React Testing Library When testing cross-component flows
End-to-End Tests Full user workflows Playwright, Cypress For critical paths before release

Improving Developer Confidence

When you have comprehensive test coverage, you feel confident making changes because failures alert you immediately. This enables:

  • Faster iteration during development cycles

  • Easier collaboration among team members

  • Smooth onboarding for new developers

  • Reduced fear around major refactors

Tests serve as living documentation of how components should behave, making them valuable resources for understanding legacy code.

Performance Optimization

Testing helps identify performance issues before they impact users. You can use tools within your test suite to:

  • Detect excessive re-renders from improper hook dependencies

  • Catch memory leaks from unsubscribed effects

  • Measure component mounting and updating timing

  • Verify optimization techniques like React.memo work correctly

Early detection of these issues prevents performance degradation as your application scales.

Common Pitfalls to Avoid

  • Over-testing implementation details: Tests should validate behavior, not internal mechanics

  • Flaky tests: Unreliable tests erode trust; ensure they run consistently

  • Ignoring edge cases: Test boundary conditions and error states

  • Missing accessibility checks: Screen readers and keyboard navigation need verification

  • Skipping cleanup: Ensure useEffect subscriptions unsubscribe properly after unmounting

Following the Rules of Hooks and properly managing test cleanup prevents many common issues in React testing.

43

What distinguishes shallow rendering from mounting in Enzyme, and how do they handle component rendering differently?

Both shallow rendering and mount rendering are Enzyme testing utilities that differ in how deeply they render React components during tests, though Enzyme is now deprecated and React Testing Library is the recommended alternative.

Read full answer

Applies toEnzyme (deprecated since 2020, maintenance ended), React Testing Library

How Shallow Rendering Works

Shallow rendering renders only the top-level component without recursively rendering its children. Child components appear as ReactElement objects or text strings rather than executing their logic or side effects.

  • Isolation: Only the target component is rendered at the surface level, preventing child components from executing their lifecycle methods

  • Performance: Faster than full rendering because it doesn’t traverse the entire component tree or mount sub-components

  • Limitations: Cannot test interaction with DOM elements beyond the immediate component boundaries, no CSS styling applied, and lifecycle methods of nested components don’t run

  • When to use: Unit testing individual components where you want to focus on one piece in isolation and verify it passes props correctly

How Mount Rendering Works

Mount rendering uses JSDOM to fully render the component along with all its children into a virtual DOM, simulating a real browser environment.

  • Full DOM: Renders everything including child components with their complete lifecycle, limited CSS through JSDOM, and real DOM interactions

  • Lifecycle execution: All lifecycle methods run as they would in a real browser environment, including componentDidMount, useEffect effects, etc.

  • Integration level: Allows testing parent-child communication, full user flow scenarios, and context providers in a realistic environment

  • When to use: Integration testing when you need to verify how components work together and test complex user interactions

Comparison Summary

Aspect Shallow Rendering Mount Rendering
Child components Replaced with text nodes or ReactElements Fully rendered with lifecycle methods
DOM simulation Minimal (single component level) Full (JSDOM-based virtual DOM)
Test speed Fast Slower due to full rendering and DOM overhead
Lifecycle methods Only target component runs All components execute completely
Use case focus Unit testing isolated logic Integration and end-to-end testing
Styling No CSS applied Limited CSS via JSDOM
Performance impact Low memory and CPU usage Higher resource consumption

Migration to React Testing Library

Since Enzyme reached end-of-life maintenance, migration to React Testing Library is strongly recommended. The key conceptual shift is testing behavior rather than implementation details.

Concern Traditional Approach React Testing Library Approach
Component depth Choose shallow vs mount Default render is sufficient for most cases
DOM querying wrapper.find() selectors screen queries by accessibility role/text
State updates wrapper.setState() User interaction callbacks with act()
Props access wrapper.props() Destructure from rendered output

Modern Best Practices

  1. Use React Testing Library instead of Enzyme for new projects as Enzyme has no active maintenance

  2. Focus on behavior, not implementation details - test what users see and interact with

  3. Prefer screen queries like queryByText, queryByRole over CSS class or element selectors

  4. Test from user perspective, checking what users see and can do, not internal component state

  5. Avoid testing private implementation details that might change with refactoring

  6. Keep tests fast - shallow rendering was faster for a reason; don’t mount unnecessarily

Common Pitfalls

  • Testing implementation details: Focus on observable behavior rather than component internals

  • Too much mocking: Over-mocking defeats the purpose of unit testing and creates fragile tests

  • CSS-dependent tests: React Testing Library discourages style-dependent assertions

  • Ignoring accessibility: Prefer semantic HTML roles over specific class names

  • Unnecessary complexity: Most tests don’t need full mount rendering; start simpler

44

What is the react-testing-library testing utility and how does it differ from Enzyme?

React Testing Library (@testing-library/react) is a lightweight testing utility for React that encourages testing components the way users interact with them, rather than inspecting implementation details such as internal state, props, or lifecycle methods.

Read full answer

Applies toReact 19, `@testing-library/react` 14, and Enzyme 3 (legacy). Enzyme is not recommended for new projects and does not reliably support React 18 or React 19.

How It Works

  • User-centric queries: Components are rendered into a jsDOM environment and located with semantic queries such as getByRole, getByText, and getByLabelText, mirroring how users find content.

  • Behavior over implementation: Only the public, observable behavior is tested; internal state, hooks, and class instance methods are deliberately avoided.

  • Automatic cleanup: Rendered components are unmounted and containers are cleared after each test, keeping tests isolated.

  • Realistic DOM interaction: Events are dispatched through the DOM, so event handling, bubbling, and default browser behavior behave as they do in production.

Enzyme Overview

Enzyme was Airbnb’s React testing utility and dominated React testing before React Testing Library. It supports shallow, mount, and static rendering and exposes component internals.

  • Implementation access: Tests can read state, props, and call instance methods directly, which often ties tests to implementation details.

  • Shallow rendering: Components can be rendered one level deep, isolating them from children but skipping real DOM integration.

  • Legacy status: Enzyme is no longer maintained for modern React. The React team recommends React Testing Library instead.

Key Differences Comparison

Aspect React Testing Library Enzyme
Philosophy Test like users interact with components Test component internals and structure
Rendering Full DOM rendering by default in jsDOM Shallow, mounted, or static rendering options
Testing focus Behavior and outcomes Implementation details such as state, props, and lifecycle
Dependencies Minimal external dependencies Requires deeper integration with React internals
Community support Active and officially favored Maintained but declining and largely legacy
Test maintenance More stable when implementation changes Fragile when component internals change

Why Choose React Testing Library

  • More resilient tests: Implementation changes do not break tests as long as user-visible behavior remains consistent.

  • Better developer habits: Encourages thinking about user interactions rather than component internals.

  • Aligns with React philosophy: Relies on React’s public APIs rather than internal mechanisms.

  • Easier refactoring: Safer to refactor components because tests depend on external behavior.

  • Active ecosystem: Strong community support and complementary testing-library packages.

Setup Considerations

  • Install @testing-library/react alongside a test runner such as Jest or Vitest.

  • For realistic user interactions, add @testing-library/user-event; in version 14, create a user with userEvent.setup() and await its actions.

  • For TypeScript, types ship with @testing-library/react; no separate type package is needed.

  • React Server Components and Next.js App Router require framework-level testing tools; @testing-library/react is primarily intended for client components.

Best Practices

  1. Prefer semantic, accessible queries such as getByRole and getByLabelText; treat data-testid as an escape hatch.

  2. Use userEvent over fireEvent when the interaction should mimic real browser behavior, such as focus, typing, and form submission.

  3. Test one observable behavior per test case.

  4. Avoid asserting on internal state, component methods, or React element structure.

  5. Add @testing-library/jest-dom for useful matchers such as toBeInTheDocument and toBeVisible.

45

Describe the concept of React portal and explain typical scenarios for its usage

React portal is a feature that renders a component tree into a DOM node outside the parent component’s DOM hierarchy while keeping it in the same React tree, so it can visually escape containers such as overflow-clipped parents without losing context, state, or event handling. It is created with createPortal from react-dom.

Read full answer

Applies toReact 19 with function components and hooks

How Portals Work

  • API: Import createPortal from react-dom and call createPortal(children, container), where container is a DOM element mounted somewhere in the document.

  • Logical vs physical placement: The portal children stay part of the parent React component tree, so they receive props, context, and state from the parent, but their DOM nodes live inside container.

  • No new component instances: React does not create a separate root or fiber tree for the portal; reconciliation proceeds as if the children were rendered inline.

Typical Usage Scenarios

  • Modals and dialogs: They must overlay the whole viewport and avoid being clipped by a parent’s overflow, position, or z-index constraints.

  • Tooltips, popovers, and select dropdowns: These floating layers need to appear above unrelated content and often calculate positions relative to the viewport rather than their trigger’s container.

  • Drawers and sidebars: Navigation panels that slide in from an edge can break out of a scrollable layout container.

  • Lightboxes and image galleries: Full-screen overlays should ignore the stacking context of the page region that opened them.

  • Toast notifications: A notification system can render into a fixed container at the top or bottom of the page regardless of which component triggered it.

  • Drag previews: During drag-and-drop, a preview element can follow the pointer outside the source list’s DOM bounds.

Event Propagation and Component Tree

Aspect Behavior
Event origin inside portal The event bubbles through the React component tree, so a parent component’s listener still fires.
DOM hierarchy The portal DOM node is not an ancestor of the trigger, so CSS selectors based on DOM ancestry do not match.
stopPropagation Calling it inside the portal stops further React bubbling exactly as it would inside normal children.
Context and state Portal children read context and state from their React parent, not from the DOM container they are rendered into.

Accessibility, Styling, and Best Practices

  • Focus management: For modals, trap focus inside the portal, close on Escape, and return focus to the element that opened the portal.

  • ARIA: Use role="dialog", aria-modal="true", and provide aria-labelledby or aria-describedby so screen readers announce the content.

  • Styling inheritance: Portal content inherits CSS from its real DOM container, not from the React parent, so reset or scope styles explicitly.

  • Container lifecycle: Ensure the target DOM node exists before rendering and remove listeners or subscriptions tied to it when the component unmounts.

  • Use sparingly: Prefer normal rendering when layout constraints allow; portals add DOM complexity and can make testing and debugging harder.

  • Server rendering: createPortal targets a DOM node, so in server-rendered applications the container should be created or hydrated in the browser; portals do not replace React Server Components.

46

What is the mechanism behind React error boundaries and how do they handle runtime errors?

Error boundaries are special React components that catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of crashing the entire application.

Read full answer

Applies toReact 19 with all versions supporting class components

What Error Boundaries Catch

Error boundaries specifically catch errors during three phases of component operation:

  • Rendering: JSX rendering errors thrown by child components

  • Constructor errors: Exceptions raised when building component instances

  • Lifecycle method errors: Errors in mount or update lifecycle hooks like componentDidMount, useEffect, etc.

They explicitly do not catch these cases:

  • Error handling inside event handlers (use try-catch instead)

  • Asynchronous operations including promises and setTimeout callbacks

  • Server-side rendering errors

  • Errors thrown within the error boundary itself

How Error Boundaries Work

Only class components can function as error boundaries because they require specific lifecycle methods. When an error occurs in a descendant component, the following sequence happens:

  1. React finds the nearest error boundary wrapping the failing component

  2. The error boundary’s static method getDerivedStateFromError executes and returns new state to set hasError flag

  3. componentDidCatch logs the error information for debugging

  4. The component tree below the boundary re-renders with the recovery state showing fallback UI

This prevents error propagation up the entire component tree and maintains application stability.

Lifecycle Method Purpose When Called
static getDerivedStateFromError(error) Updates state for fallback UI Before every render when error exists
componentDidCatch(error, errorInfo) Logs error details and handles recovery After catching an error

Key Features

  • Isolation: Errors stay contained within the boundary’s subtree without affecting the rest of the app

  • Customizable fallback: Provide custom UI via fallback prop or component

  • Error logging: Access detailed error information in componentDidCatch for debugging

  • Selective placement: Wrap critical sections independently rather than one global boundary

Best Practices and Common Pitfalls

  • Place boundaries around dynamic content and user-generated components rather than static parts

  • Use multiple smaller boundaries instead of one large boundary covering everything

  • Avoid putting boundaries around all components to prevent masking legitimate bugs during development

  • Remember that error boundaries cannot recover from network failures or API errors in client code

  • For React 19 functional components, there’s no direct equivalent yet—error boundaries remain class-only

  • Consider third-party libraries like react-error-boundary if you need functional component support

Creating effective error boundaries improves user experience during failures and provides valuable debugging information for teams.

47

How does server-side rendering work in React and what are the approaches for implementing it?

Server-side rendering (SSR) is a technique where a React component tree is rendered into HTML on the server and shipped to the browser before the client bundle executes, so users receive a fully formed page on first paint instead of a blank shell.

Read full answer

Applies toReact 19 with SSR frameworks such as Next.js App Router or React Router; the underlying `ReactDOMServer` APIs are shared with React 18.

How It Works

  1. The server receives a request and renders the requested route’s component tree using ReactDOMServer APIs such as renderToString or renderToPipeableStream.

  2. Data needed for the page is fetched during rendering, often through framework loaders or Server Components.

  3. The resulting HTML is sent to the browser together with a small client script and serialized state.

  4. The browser displays the HTML immediately, then the client bundle loads and React hydrates it.

  5. Hydration attaches event listeners and reconciles the client tree with the server markup; afterward, navigation and state updates behave like a normal client-side app.

React 19 continues the streaming model introduced in React 18: the server can send HTML in chunks as they become ready, and Suspense boundaries let slow data or code blocks defer without blocking the entire page.

Implementation Approaches

React itself does not ship routing or server infrastructure, so SSR is implemented in two ways:

  • Manual ReactDOMServer: Call renderToPipeableStream yourself, wire up a Node server, serialize data, and hydrate with hydrateRoot. This works but duplicates routing, code-splitting, and data-handling logic.

  • Framework-supported SSR: Next.js App Router, React Router, and similar frameworks provide file-based routing, loaders, streaming, and hydration tooling out of the box. This is the recommended path for new projects.

Frameworks expose several rendering strategies:

Strategy Render timing Best for
Server-Side Rendering (SSR) Every request Personalized, real-time, or authenticated content
Static Generation (SSG) Build time Marketing pages, blog posts, documentation
Incremental Static Regeneration (ISR) Build time plus periodic revalidation Large catalogs that change slowly
Streaming SSR Server streams chunks as ready Pages with slow dependencies or many Suspense boundaries

Create React App does not support SSR and is deprecated for new projects. For SSR, choose a framework; Vite is a solid option for client-only applications.

Best Practices

  • Fetch data on the server: Avoid relying on useEffect fetching for the initial render, because that produces a client-only render and defeats SSR.

  • Guard browser globals: window, document, and localStorage are unavailable during server rendering; access them in effects or after hydration.

  • Stream and suspend: Use Suspense boundaries and streaming to send fast parts of the page first instead of waiting for every query.

  • Serialize state safely: Embed only the data the client needs, and never include secrets, API keys, or internal tokens.

  • Prevent hydration mismatches: Ensure server and client produce identical markup; conditional browser-only markup during the first render causes mismatches.

  • Cache and revalidate: Cache database and API responses, and set appropriate revalidation or cache-control headers.

Common Limitations

  • Server cost: Each SSR request consumes CPU and memory, unlike static hosting.

  • Browser API gaps: Code that assumes a browser environment must be deferred or conditionally executed.

  • Hydration complexity: Mismatched markup, missing providers, or incorrect data serialization can trigger hydration errors.

  • Time to interactive: The page is visible early, but users still wait for JavaScript to hydrate before they can interact.

48

How do React Suspense and lazy loading collaborate to optimize component rendering and bundle management?

React Suspense and lazy loading provide a declarative mechanism for code splitting and loading-state management, letting an app defer rendering a component until its JavaScript bundle has been fetched. By showing a fallback UI at a Suspense boundary while the chunk loads, they shrink the initial bundle and keep the interface responsive.

Read full answer

Applies toReact 19 (Client Components)

How Lazy Loading Works

  • React.lazy: Declared at module scope, this function wraps a dynamic import() and exposes the module’s default export as a regular component. It is supported only in Client Components.

  • Dynamic imports: Bundlers such as Webpack or Vite split code at the import() call, producing a separate chunk that is requested only when the component renders.

  • Client-only boundary: React.lazy does not work inside Server Components; in frameworks like Next.js the lazy component must live in a Client Component rendered beneath a Suspense boundary.

How Suspense Manages State

  • Suspense boundary: <Suspense fallback={...}> wraps lazy components and renders the fallback until the wrapped tree signals it is ready.

  • Declarative readiness: Components communicate their loading state upward to the nearest boundary, so developers do not need manual useState / useEffect loading flags.

  • Granular nesting: Multiple nested boundaries let independent regions show their own fallbacks instead of blocking the whole screen.

Code Splitting Benefits

  • Initial load performance: Users download only the JavaScript needed for the first view; heavy features load on demand, improving time-to-interactive.

  • Caching efficiency: Smaller, feature-scattered chunks change less often than one monolithic bundle, so browsers cache unchanged code across deployments.

  • Memory and parsing cost: Low-end devices avoid parsing unused code paths during initial render.

  • Real-world example: A dashboard can render its layout and summary cards immediately while a heavy analytics chart loads behind a Suspense boundary.

Error Handling

  • Error boundaries, not Suspense: Suspense handles loading promises only. If a chunk fails to load or a module throws, an Error Boundary is required to show error UI.

  • Retry logic: Store the failure in the Error Boundary’s state and expose a retry button so users can reload the failed chunk without a full page refresh.

Best Practices and Pitfalls

  • Avoid over-splitting: Too many tiny chunks increase round-trips and can hurt high-latency connections; balance chunk size against feature usage.

  • Stable fallbacks: Make the fallback match the loaded component’s layout dimensions to prevent cumulative layout shift (CLS).

  • Prefer Server Components for data: In React 19 frameworks such as Next.js, fetch data in Server Components when possible; React.lazy remains a Client-Component tool, while Server Components stream HTML.

  • Default export requirement: The imported module must export the component as default, or React.lazy will throw during resolution.

49

In what ways does server-side rendering enhance search engine optimization for React-based web applications?

Server-Side Rendering (SSR) significantly improves SEO by sending fully rendered HTML to the client, allowing search engine crawlers to index content immediately without executing JavaScript. This contrasts with client-side rendering, where crawlers might receive an empty shell before React hydrates the page. By generating the initial markup on the server, React applications ensure that titles, descriptions, and body text are visible in the initial HTTP response.

Read full answer

Applies toReact 19 (using ReactDOMServer or frameworks like Next.js)

How SSR Solves the Crawling Issue

Search engine bots traditionally fetch HTML and parse it directly. In a standard Client-Side Rendering (CSR) setup, the initial HTML contains only a root div and script tags. The content is injected later by JavaScript. While modern crawlers can execute JS, it is slower and less reliable than parsing static HTML. SSR moves the rendering logic to the server, populating the HTML string before it leaves the server. This guarantees that the content is present in the initial payload, reducing the time-to-content for both users and bots.

React 19 Server APIs

React 19 provides low-level APIs via ReactDOMServer for manual SSR implementations. The primary functions include renderToString for synchronous rendering and renderToPipeableStream for streaming responses in Node.js environments. These APIs convert React element trees into HTML strings on the server side. However, building a full SSR solution from scratch involves handling routing, data fetching, and hydration manually. Using frameworks ensures proper handling of routing and data hydration patterns that are error-prone when implemented manually. Most developers use frameworks like Next.js App Router, which abstract these complexities and support React Server Components (RSC). Note that RSC is distinct from traditional SSR and requires framework support.

Hydration and Interactivity

SSR alone produces static HTML. To make it interactive, React must “hydrate” the DOM on the client. This process attaches event handlers to the existing server-rendered nodes without regenerating them. In React 19, hydrateRoot is the standard method for this, replacing the legacy hydrate method. Mismatch between server and client markup (e.g., using window during server render) causes hydration errors. Ensure components render identically on both sides during the initial pass. If the server sends different HTML than the client expects, React will warn and potentially discard the server markup, negating the SEO benefit.

Best Practices and Pitfalls

  • Dynamic Meta Tags: Use libraries like react-helmet or framework-specific head management to ensure titles and descriptions are server-rendered.

  • Data Fetching: Fetch data on the server before rendering to avoid loading states in the initial HTML.

  • Framework Choice: For new projects, avoid Create React App (deprecated). Use Vite for CSR or Next.js for SSR/RSC.

  • Performance: SSR increases server load. Consider Static Site Generation (SSG) or Incremental Static Regeneration (ISR) for content that doesn’t change per request.

  • Hydration Errors: Avoid browser-only APIs (like localStorage) during the render phase. Check for typeof window !== 'undefined' before usage.

  • Security: Always validate server responses before rendering to prevent injection attacks and ensure data integrity.

50

What mechanism allows JavaScript code to communicate with platform-specific implementations in React Native?

Bridging JavaScript to platform-specific code in React Native is accomplished using Native Modules, with the current standard being TurboModules within the New Architecture. This approach uses the JavaScript Interface (JSI) to let JavaScript hold direct references to native host objects and invoke methods synchronously, replacing the legacy serialized asynchronous bridge.

Read full answer

Applies toReact Native 0.73+ with New Architecture enabled

Legacy Bridge vs New Architecture

The legacy bridge passed JSON-serialized messages between the JavaScript and native threads through a batched queue. Because serialization and thread hopping were required, calls were asynchronous and could not return values directly, which created latency for animations, sensors, and heavy computation.

The New Architecture replaces that queue with JSI, which exposes native objects to the JavaScript runtime as host objects. TurboModules are native modules registered through JSI and instantiated lazily, so JavaScript can call them synchronously with minimal overhead.

Aspect Legacy Bridge New Architecture (TurboModules + JSI)
Communication Serialized JSON messages over an async queue Direct host-object calls through JSI
Synchronous calls Not possible Supported for many operations
Module discovery Eagerly registered at startup Lazy registration and instantiation
Type safety Runtime checks only Generated specs checked at compile time

How TurboModules Work

  1. Define a TypeScript specification that describes the module’s methods, constants, and return types. The specification must extend TurboModule so Codegen recognizes it.

  2. Run Codegen during the build. It produces C++ glue code plus platform-specific interfaces: an Android Java/Kotlin interface and an iOS Objective-C/Swift protocol.

  3. Implement the native side by writing a class that conforms to the generated interface and registers it with the ReactNativeHost module registry.

  4. Call from JavaScript by retrieving the module with TurboModuleRegistry.getEnforcing<Spec>('ModuleName'), which throws if the native implementation is absent.

  5. Handle results and errors by returning values directly, returning a Promise, or rejecting the promise so JavaScript can catch the error consistently.

Codegen and Type Safety

Codegen turns the TypeScript spec into compiled contracts, so mismatched method signatures or argument types fail during compilation rather than at runtime. Modern React Native projects do not usually hand-write Codegen steps: the Android Gradle plugin and iOS CocoaPods setup run it automatically before native binaries are compiled. Skipping or misconfiguring it causes missing-module errors at startup.

Best Practices

  • Minimize cross-boundary calls: each call still has overhead, so batch related operations into a single native method.

  • Use native event emitters for streams: sensor data, location updates, or device motion should push events to JavaScript instead of being polled.

  • Keep the main thread free: long-running native work should run on background threads to avoid UI jank and JavaScript thread blocking.

  • Request native permissions explicitly: camera, location, storage, and Bluetooth permissions must be requested and denied gracefully on each platform.

  • Test native logic independently: unit-test the Kotlin/Swift/C++ implementation, then verify the JavaScript boundary with integration tests such as Jest or Detox.

51

How would you define Redux, and what are the key differences between using it and React's built-in Context API for state management?

Redux is a dedicated state management library that centralizes application logic in a single store, typically configured via Redux Toolkit, while React’s Context API is a built-in feature designed primarily to avoid prop drilling for values like themes or user authentication. The choice between them depends on the complexity of the state logic and the performance requirements of the component tree.

Read full answer

Applies toReact 19, Redux Toolkit 2.x

Core Concepts

Redux follows a unidirectional data flow where state is stored in a single immutable store. Changes are made by dispatching actions processed by reducers to produce a new state. Modern usage relies on Redux Toolkit (RTK), which simplifies setup via createSlice and integrates with React using useSelector and useDispatch from react-redux. The Context API uses createContext, a Provider component to hold values, and the useContext hook to consume them. In React 19, the new use hook can also read context within Server Components when using a framework like Next.js App Router. For intermediate complexity, React’s built-in useReducer combined with Context often suffices without the weight of Redux.

Key Differences

Feature Redux (Toolkit) Context API
Purpose Complex global state logic Prop drilling avoidance
Performance Optimized via selectors Re-renders all consumers on change
Boilerplate Moderate (RTK reduces this) Minimal
Ecosystem Rich (middleware, devtools) Built-in, limited
Learning Curve Steeper Gentle

Performance Implications

A critical distinction lies in how updates trigger re-renders. When a Context value changes, every component consuming that context re-renders, even if they only use a small part of the value. Redux mitigates this through useSelector, which by default performs a strict reference equality check (===) and only re-renders components whose selected value has changed; you can supply a custom equality function such as shallowEqual when comparing objects. For high-frequency updates like form inputs or real-time dashboards, Redux generally scales better without requiring excessive useMemo or useCallback optimizations.

Selection Guidelines

Use Context API for state that is relatively static or changes infrequently, such as theme toggles, language localization, or authenticated user details. If your application is small to medium-sized and you want to avoid external dependencies, Context is sufficient. Opt for Redux when you need complex state transitions, middleware integration, or robust developer tooling for time-travel debugging. It is preferable for applications with large teams where predictable state updates are crucial. If you find yourself writing excessive useMemo or useCallback hooks to prevent Context re-renders, it is a strong signal that Redux might be the more maintainable solution. It is acceptable to use both; Context for UI preferences and Redux for business data.

Best Practices

  • Start Simple: Begin with Context API or local state (useState, useReducer) before introducing Redux.

  • Use Redux Toolkit: Never write legacy Redux boilerplate manually; RTK is the official recommended approach.

  • Avoid Over-Globalization: Keep state as local as possible; global state should only be used when truly shared across many components.

  • Split Contexts: If using Context, split into smaller, domain-specific providers rather than one giant global context to minimize re-renders.

52

How does the state management cycle operate within a Redux architecture when integrated with modern React function components?

The Redux state management cycle in modern React function components follows a unidirectional data flow in which components dispatch plain actions to a centralized Redux Toolkit store, reducers produce new immutable state snapshots, and subscribed components re-render via useSelector only when their selected data changes.

Read full answer

Applies toRedux Toolkit 2.x with React 19 and react-redux 9.x

Core Architecture Components

Modern Redux is built around Redux Toolkit (RTK), which removes most boilerplate: - Store: the global state tree, created with configureStore, which combines slice reducers, adds useful middleware, and enables Redux DevTools. - Slice: created with createSlice; bundles reducer logic and generated action creators, eliminating manual action-type constants. - Action: a plain object describing what happened; slice methods generate these automatically. - Reducer: a pure function that returns a new state snapshot. Inside RTK reducers, Immer-style mutation syntax is safe. - Selector: a function that reads a slice of state, often memoized with createSelector.

The Data Flow Cycle

  1. A user interaction or effect in a function component calls a slice action creator through dispatch.

  2. The dispatched action is sent to the store’s root reducer.

  3. The root reducer delegates to each slice reducer, which returns a new state slice without mutating the previous state.

  4. The store replaces the old state tree with the new snapshot.

  5. All subscribers are notified; useSelector compares its previous and next selected values using shallow equality.

  6. Only components whose selected value changed re-render and read the fresh state.

Connecting React Components

The application is wrapped in <Provider store={store}>, making the store available through React context. Function components use hooks instead of the legacy connect higher-order component: - useDispatch: returns store.dispatch for sending actions. - useSelector: subscribes the component to the store and returns the selected state fragment. Pass a memoized selector to avoid expensive recalculations. - useStore: returns the store instance directly; useful for advanced scenarios but rarely needed in typical components.

Best Practices and Common Pitfalls

  • Keep local state local: use useState or useReducer for UI state confined to one component; reserve Redux for state many components need or for server-cache state.

  • Normalize global data: keep entities flat and indexed by ID to avoid duplication and simplify updates.

  • Do not mutate state outside reducers: Immer-style mutation is safe only inside RTK reducers; elsewhere always return new objects or arrays.

  • Memoize selectors: wrap expensive or derived selectors in createSelector so recomputation happens only when relevant inputs change.

  • Avoid legacy patterns: manual action-type constants, switch reducers, and the connect HOC are replaced by RTK slices and hooks.

  • Prevent unnecessary renders: avoid selecting large objects or creating new object literals directly in useSelector; select primitive values or memoized results instead.

54

What are the steps to initialize a new React application without any pre-existing template or boilerplate?

To initialize a React application without any pre-existing template or boilerplate, manually create the project directory, install react, react-dom, and Vite, write the HTML shell and root component yourself, and add build scripts to package.json.

Read full answer

Applies toReact 19 with Vite 6 (manual setup)

Recommended Tooling

Create React App is deprecated and should not be used for new projects. For a truly from-scratch setup, Vite is the mainstream choice because it adds only a dev server and bundler, not a file structure. Next.js is a full-stack framework and ships with app router boilerplate, so it does not satisfy “no template.”

  • Vite: Provides HMR, a fast dev server, and production builds through @vitejs/plugin-react.

  • React 19: Uses the modern JSX transform, so import React from 'react' is no longer required in JSX files.

Manual Setup Steps

  1. Create and enter the project folder: mkdir my-app && cd my-app.

  2. Initialize a package manifest: npm init -y.

  3. Install React: npm install react react-dom.

  4. Install Vite and the React plugin as dev dependencies: npm install -D vite @vitejs/plugin-react.

  5. Create index.html at the root with a <div id="root"></div> and <script type="module" src="/src/main.jsx"></script>.

  6. Create src/main.jsx and render your root component with createRoot.

  7. Create src/App.jsx as a function component.

  8. Add scripts to package.json: "dev": "vite", "build": "vite build", "preview": "vite preview".

  9. Create vite.config.js to register the React plugin and optionally set base for subdirectory deploys.

  10. Start the dev server: npm run dev.

Minimal Project Structure

my-app/
├── package.json
├── index.html
├── vite.config.js
└── src/
    ├── main.jsx
    └── App.jsx

Code Example

index.html loads the entry module:

<!DOCTYPE html>
<html>
  <head><title>My App</title></head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

src/main.jsx mounts the app:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
);

src/App.jsx demonstrates hooks with correct dependencies:

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  // Runs on mount and whenever count changes
  useEffect(() => {
    document.title = `Clicked ${count} times`;
  }, [count]);

  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

vite.config.js enables JSX compilation:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
});

Production and Deployment

Run npm run build to emit a dist folder and serve it with any static host. If deploying to a subdirectory, set base: '/my-app/' in vite.config.js. Use .env variables prefixed with VITE_ and read them through import.meta.env.VITE_NAME; never store secrets in client code.

Common Pitfalls

  • Missing React plugin: Without @vitejs/plugin-react, JSX files are not transformed and the build fails.

  • Wrong effect dependencies: An empty dependency array [] runs once on mount; omitting the array runs after every render.

  • Browser-only APIs: Access document or window inside useEffect or event handlers, not during render.

  • Multiple root elements: Return a single element or fragment from a component; React 19 still does not allow adjacent JSX without a wrapper.

  • Legacy tooling: Avoid create-react-app and class components unless maintaining old code.

55

Explain the role of Babel and why it is commonly integrated into React toolchains.

Babel is a JavaScript compiler/transpiler that converts modern JavaScript syntax, JSX, and TypeScript into backward-compatible JavaScript so browsers and runtimes can execute it. It is commonly integrated into React toolchains because React components are written in JSX and often use cutting-edge language features that require transformation before deployment, though modern frameworks increasingly hide or replace Babel with faster compilers.

Read full answer

Applies toBabel 7.x with React 19 and modern bundlers (Vite, Next.js)

Core Functionality

  • Transpilation: Babel parses source into an AST, applies plugins, and emits older JavaScript. Without explicit targets, @babel/preset-env can emit ES5-style code, so targets should be set deliberately to avoid unnecessary downleveling.

  • JSX support: @babel/preset-react converts JSX into React.createElement calls or, with React 17+ and the new JSX runtime, into functions from react/jsx-runtime, so component files no longer need a React import solely for JSX.

  • Experimental syntax: Stage-3 proposals such as decorators can be used before browsers support them via plugins.

  • TypeScript stripping: @babel/preset-typescript removes type annotations but does not type-check; TypeScript projects still need tsc or a language server for type safety.

Why React Toolchains Use It

  • JSX is not valid JavaScript: Browsers cannot parse <h1>Hello</h1> inside a script, so a build step is mandatory.

  • Consistent output: Babel normalizes syntax across browsers, letting teams write modern code while supporting defined browser lists.

  • Plugin ecosystem: Custom transformations, styled-components, or internationalization tooling often ship Babel plugins.

  • Framework defaults: Create React App exposed Babel directly; it is deprecated, and new projects should use Vite or a framework.

Modern Tooling Landscape

Compiler Typical use Babel still useful when
esbuild Vite dev server JSX/transpilation Custom non-standard syntax or fine-grained plugin control
SWC Next.js default production builds Legacy plugin ecosystem or complex macros
Babel Manual configs, Jest, Storybook, legacy setups Need for mature plugin ecosystem or precise target control

React 19 itself does not require Babel; the bundler chooses the compiler. Understanding that transpilation remains required even when Babel is absent is important for interviews.

Configuration Essentials

  • Use babel.config.js for project-wide configuration, especially in monorepos; .babelrc applies only to files under its directory.

  • Install @babel/preset-env and @babel/preset-react (plus @babel/preset-typescript for .tsx) and let the bundler invoke them.

  • Specify targets in preset-env instead of relying on defaults; this prevents bloated output for modern browsers.

  • Let frameworks manage Babel unless you need custom plugins, because manual configuration adds maintenance cost.

Best Practices

  • Do not manually configure Babel unless framework defaults cannot express your transformation.

  • Avoid over-polyfilling; set explicit browser targets and use useBuiltIns: "usage" only when supporting older environments.

  • Keep build dependencies updated; compromised build tooling can inject malicious code into production bundles.

  • Remember that React Server Components and server actions are transpiled separately from client bundles in frameworks like Next.js.

  • Pair Babel with a type checker when using TypeScript, since Babel only strips types.

56

Define Webpack and explain how it supports React application development

Webpack is a static module bundler that combines JavaScript files and their dependencies into optimized bundles ready for production deployment. In React development, Webpack manages component imports, transpilation through Babel, CSS handling, asset optimization, and code splitting to deliver efficient client-side applications.

Read full answer

Applies toWebpack 5 with React 19

How It Works

Webpack processes your React project by resolving all module dependencies and creating optimized output bundles:

  • Entry Point: Starts from your main file (typically index.js or main.jsx) and recursively finds all dependencies

  • Loaders: Transforms non-JavaScript files—Babel loads JSX syntax into valid JavaScript, CSS loaders process styles, image loaders handle media assets

  • Plugins: Extend functionality with HTML generation, environment variables, minification, and bundle analysis

  • Output Generation: Produces one or more bundled files configured for development or production builds

Core Capabilities

Feature Purpose Common Setup
Module Bundling Resolves imports/exports across files N/A (built-in)
Tree Shaking Eliminates unused code ES6 modules + mode: 'production'
Code Splitting Loads chunks on demand Dynamic import()
Hot Module Replacement Updates running app without refresh HMR plugins enabled
Asset Management Handles images/fonts/styles Assets/modules in webpack.config.js
Environment Configuration Sets process.env values DefinePlugin or dotenv

Role in React Projects

Webpack provides essential infrastructure for React applications:

  • JSX Transformation: Converts .jsx syntax to browser-compatible JavaScript via Babel loaders

  • Component Composition: Imports components as npm packages or relative paths

  • State Management Integration: Bundles Redux, Zustand, Context API providers

  • Routing Support: Packages react-router-dom or similar navigation libraries

  • Styling Options: Processes CSS Modules, SCSS, styled-components, Tailwind configurations

  • Bundle Optimization: Splits vendor code from application code for better caching

  • Development Experience: Provides dev servers with auto-reload for rapid iteration

Modern Development Practices

While Webpack remains functional, several considerations apply to current React development:

  • Create React App is deprecated: Replace with Vite, Next.js, or custom Webpack setup using modern tooling

  • Alternative Bundlers: Vite uses esbuild for faster startup; Next.js uses Webpack internally for Server-Side Rendering

  • React Server Components: Require frameworks like Next.js that manage bundling at the server level

  • SWC Acceleration: Use SWC over Babel in loader chains for improved build performance

  • TypeScript Integration: Configure TypeScript compiler options through tsconfig.json alongside Webpack

When setting up new projects, consider using Vite for client-side React apps due to its speed, or Next.js App Router for full-stack applications requiring React Server Components. Traditional Webpack remains viable for custom configurations or legacy migration scenarios.

57

Explain the mechanism behind hot module replacement in React development workflows.

Hot module replacement (HMR) is a development feature that allows you to see code changes in your running application immediately without requiring a full page reload. This capability relies on specialized bundlers like Vite or Webpack with specific plugins to track dependencies and inject updated modules into your running React application while preserving application state.

Read full answer

Applies toReact 19 with Vite or Next.js

How It Works

Hot Module Replacement operates through several coordinated mechanisms during development:

  • Module Dependency Tracking: The bundler analyzes all imports and exports in your codebase to build a dependency graph before compilation begins.

  • Change Detection: When you modify a file, the bundler identifies only the affected modules rather than rebuilding the entire application.

  • Runtime Updates: A runtime client communicates between the browser and server to receive updated module information and apply changes dynamically.

  • State Preservation: Unlike traditional page refreshes, HMR keeps component state intact so you maintain your current position and data in the application.

  • Fallback Strategy: If a module cannot be hot-reloaded (due to certain mutations), the bundle automatically performs a full page refresh as a safety measure.

Modern Bundler Requirements

Since Create React App is deprecated, the primary tools supporting HMR are:

Tool HMR Support Minimum Version Notes
Vite Native Vite 5+ Default for new React projects, fastest HMR
Webpack Requires Plugin Webpack 5 + react-refresh Legacy option, slower than Vite
Next.js App Router Built-in Next.js 13+ Server-focused, partial hydration
CRA Deprecated N/A No longer maintained, remove from recommendations

Vite provides superior HMR performance because it leverages native ESM in the browser during development, avoiding the bundling step for each change.

Key Features of React Refresh

React 19 uses React Refresh specifically for component-level updates:

  • Component Identity Tracking: Each function component maintains an internal identifier that helps determine when state should persist across updates.

  • Safe Re-rendering: Components update their rendered output while maintaining their hooks and state values.

  • Error Boundaries: Runtime errors prevent successful HMR and trigger manual refresh instead, protecting against inconsistent states.

  • Automatic Batching: Internally managed updates ensure efficient rendering cycles without manual intervention from the developer.

Best Practices

Follow these guidelines to ensure reliable hot module replacement:

  1. Avoid Module Mutation: Never modify exported functions or classes directly after they’re imported. Always export fresh implementations from source files.

  2. Respect Dependency Changes: Modifying dependencies in package.json requires stopping the dev server even with HMR enabled.

  3. Test State Preservation: Verify that form inputs, scroll positions, and temporary UI states survive hot updates in your actual application flow.

  4. Minimal Configuration Required: Standard React templates come pre-configured with HMR; avoid complex overrides unless debugging specific build issues.

  5. Production Configuration: Ensure HMR is disabled in production builds where security and performance matter more than developer experience.

Common Pitfalls

Be aware of scenarios where HMR may not behave as expected:

  • Adding or removing environment variables requires a full server restart

  • TypeScript compiler errors will block hot updates until resolved

  • Certain third-party packages may not implement proper exports for safe HMR

  • Browser extensions debugging tools occasionally interfere with hot reload detection

When HMR fails silently, consult the browser console for specific error messages indicating which module failed to reload and why.

58

What capabilities does create-react-app provide, and what steps are involved in ejecting from it?

create-react-app is a deprecated scaffolding tool that provided a zero-configuration build setup for React applications; its eject command permanently exposed the hidden Webpack, Babel, and ESLint configuration. Because it is no longer maintained, new projects should use Vite or a framework such as Next.js, and existing projects should migrate rather than eject.

Read full answer

Applies tocreate-react-app 5.x (deprecated)

Core Capabilities

  • Zero-configuration bundling: shipped Webpack, Babel, and ESLint presets so no build configuration was required.

  • TypeScript support: npx create-react-app my-app --template typescript generated a TypeScript-ready project with type checking.

  • Development server: npm start served the app with hot module replacement and a runtime error overlay.

  • Production builds: npm run build produced optimized, tree-shaken, code-split static assets with content hashes.

  • Testing: preconfigured Jest plus @testing-library/react, @testing-library/jest-dom, and @testing-library/user-event, run with npm test.

  • Environment variables: .env, .env.local, and NODE_ENV-specific files injected only REACT_APP_* values into the bundle at build time.

  • Standard layout: enforced public/ for static assets and src/ for application code, with src/index.js as the entry point.

The Eject Process

  1. Commit or back up your work; eject is irreversible.

  2. Run npm run eject (or yarn eject) from the project root.

  3. Confirm the interactive prompt.

  4. CRA copies its internal configuration—Webpack, Babel, ESLint, Jest, and the script runners—into your project root.

  5. It rewrites package.json scripts to point at the generated files and removes the react-scripts dependency.

  6. You now own the build tooling and must apply dependency and security updates yourself.

Why Ejecting Is No Longer Recommended

Concern Effect
Deprecation create-react-app is deprecated and receives no new features
Maintenance burden Ejected configuration becomes your responsibility to patch and upgrade
Lost automation You no longer benefit from react-scripts updates
One-way operation There is no supported path back to the managed setup
Better alternatives Vite and modern frameworks offer more control without ejecting

Customization Without Ejecting

  • Use .env.* files for environment-specific values, remembering the REACT_APP_ prefix.

  • Add or modify static assets and HTML under public/.

  • Extend workflows with extra npm scripts in package.json.

  • For deeper Webpack or Babel changes without ejecting, community tools such as react-app-rewired or craco were used, but they add their own maintenance cost.

  • For most teams, migrating to Vite or a framework is safer than fighting CRA’s limits.

Modern Alternatives

  • Vite: the fastest option for new SPAs, with native ES modules and fast HMR; scaffold with npm create vite@latest.

  • Next.js: a full-stack framework with routing, server rendering, and image optimization; scaffold with npx create-next-app@latest.

  • Remix: a framework focused on web standards and nested routing, useful when data loading and forms are central.

Best Practices

  • Do not start new projects with create-react-app.

  • Treat eject as a last resort for legacy codebases that cannot be migrated immediately.

  • Prefer incremental migration to Vite or a framework over ejecting.

  • Verify build output, routing, and environment-variable behavior after migrating, because configuration semantics differ between toolchains.

  • If you must keep a CRA app, avoid ejecting and pin react-scripts to a known version while planning migration.

59

What defines Axios, and in what scenarios is it preferred compared to the built-in fetch API within React development?

Axios is a promise-based HTTP client for JavaScript environments, including browsers and Node.js, that simplifies requests compared with fetch by handling JSON automatically, rejecting non-2xx responses, supporting interceptors, and exposing request cancellation helpers. In React 19, Axios is typically used in function components and custom hooks with useState and useEffect; class components are legacy. Server Components and Server Actions require a framework that supports them, such as Next.js App Router.

Read full answer

Applies toReact 19, Axios 1.x

Key Features of Axios

  • Promise-based requests: callers can await responses or use .then and .catch.

  • Automatic JSON transformation: plain objects are sent as JSON, and JSON responses are available on response.data.

  • Configurable status handling: by default, Axios rejects non-2xx responses, while fetch resolves them.

  • Interceptors: request and response interceptors centralize auth headers, logging, retries, and global error redirects.

  • Cancellation: Axios 1.x supports AbortController; CancelToken is deprecated and should be replaced with AbortController.

  • Shared clients: axios.create can store a common baseURL, headers, and timeout.

Axios Compared with fetch

Concern Axios fetch
JSON responses Automatically transformed into response.data Parse manually with response.json()
HTTP status errors Rejects outside the configured validateStatus range Resolves 4xx/5xx; check response.ok or response.status
Interceptors Built-in API for request and response phases None built-in; wrap calls in helper functions
Cancellation AbortController; legacy CancelToken is deprecated AbortController
Progress Upload/download progress options in supporting browsers Requires stream handling or lower-level event wiring
Runtime availability Works in browsers and Node.js, including older runtimes without global fetch Built into modern browsers and recent Node.js environments

Scenarios Where Axios Is Preferred in React

  • API clients with cross-cutting concerns: interceptors make token refresh, authorization headers, and centralized 401 handling easier than repeating logic around fetch.

  • Teams want a single error model: Axios rejects for both network problems and unexpected HTTP statuses, reducing forgotten response.ok checks.

  • Browser file uploads: Axios progress events simplify showing upload percentage without manually reading streams.

  • Shared configuration: an Axios instance keeps base URL, timeout, and headers away from component code.

  • Simple or low-dependency apps: fetch may be enough for one or two public GET requests, especially when bundle size matters.

React Integration Patterns

  • Function components: call Axios from useEffect or user-event handlers; include request inputs in the effect dependency array.

  • Custom hooks: encapsulate data, loading, error, and cancellation logic so multiple components can reuse the same request pattern.

  • Cleanup: return cleanup from the effect and abort the request with AbortController to avoid updates after navigation or dependency changes.

  • Framework-dependent server patterns: React Server Components and Server Actions require a framework; in those server-side paths, fetch or Axios may both work, but React’s component model, not Axios, decides the flow.

Best Practices and Pitfalls

  • Create one configured Axios instance: centralize baseURL, timeout, and auth refresh rather than hardcoding URLs in each component.

  • Use AbortController, not CancelToken: the latter is deprecated and has no place in new Axios code.

  • Handle errors explicitly: network failures, timeouts, HTTP errors, and cancellation should not all be treated the same.

  • Do not expose secrets in client bundles: browser code cannot safely store API signing secrets; use HTTP-only cookies, a server route or proxy, or short-lived tokens in memory.

  • Prefer a server-state library for complex UIs: caching, deduplication, background refetching, and stale-data control are usually better in TanStack Query or a similar tool than in many separate useEffect calls.

60

How should a WebSocket connection be managed inside a React application?

WebSocket connection handling in React should live in a client component, using useEffect to open and close the socket and useRef to keep a stable reference for sending messages.

Read full answer

Applies toReact 19 function components and hooks; browser and React Native `WebSocket` APIs; Next.js App Router Client Components where relevant.

How It Works

  • Open in useEffect: new WebSocket(url) is a side effect, so it runs after render, not during render.

  • Depend on url: changing the endpoint closes the old socket and opens a new one.

  • Close in cleanup: remove handlers and call socket.close() on unmount or before reconnecting to prevent leaks.

  • Keep the socket in a ref: socketRef.current is available to send logic without causing unnecessary re-renders.

State and Messages

  • Use state for UI data: status and messages should be component or store state.

  • Use functional updates: call setMessages((current) => [...current, message]) so each message appends without stale state.

  • Avoid mutation: create a new array or update an external store.

  • Scale carefully: for large message streams, use a provider or useSyncExternalStore instead of keeping every message in one component’s state.

Sharing, Frameworks, and Reconnection

  • Client-only API: WebSocket is not available during server rendering. In Next.js App Router, use 'use client' components for socket logic.

  • Project tooling: prefer Vite or a framework such as Next.js; Create React App is deprecated for new projects.

  • Reconnect deliberately: on onclose or onerror, retry with exponential backoff and a retry limit, not an infinite immediate loop.

  • Guard stale updates: use a disposed flag so old socket callbacks cannot update a component after cleanup.

  • Use secure endpoints: prefer wss:// in production and parse or validate server payloads before using them.

Code Example

import { useCallback, useEffect, useRef, useState } from "react";

export function useWebSocket(url) {
  const [status, setStatus] = useState("idle");
  const [messages, setMessages] = useState([]);
  const socketRef = useRef(null);

  useEffect(() => {
    if (!url) {
      setStatus("idle");
      return undefined;
    }

    let disposed = false;
    let socket;

    try {
      // External side effect: open the socket after render.
      socket = new WebSocket(url);
    } catch (error) {
      // Invalid URL or unsupported protocol.
      setStatus("error");
      return undefined;
    }

    setStatus("connecting");
    socketRef.current = socket;

    socket.onopen = () => {
      if (!disposed) setStatus("open");
    };

    socket.onmessage = (event) => {
      if (disposed) return;
      const message =
        typeof event.data === "string" ? event.data : "[non-text message]";
      // Functional update avoids stale closures.
      setMessages((current) => [...current, message]);
    };

    socket.onerror = () => {
      if (!disposed) setStatus("error");
    };

    socket.onclose = () => {
      if (!disposed) setStatus("closed");
    };

    return () => {
      // Cleanup: detach handlers and close the socket.
      disposed = true;
      socket.onopen = null;
      socket.onmessage = null;
      socket.onerror = null;
      socket.onclose = null;
      socket.close();
      socketRef.current = null;
    };
  }, [url]);

  const send = useCallback((text) => {
    const socket = socketRef.current;
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(text);
    }
  }, []);

  return { status, messages, send };
}

Best Practices and Pitfalls

  • Send only when ready: check socket.readyState === WebSocket.OPEN before calling socket.send(...).

  • Handle StrictMode: React 19 development StrictMode can mount and remount effects, so cleanup must be idempotent.

  • Parse JSON safely: if the server sends JSON, wrap JSON.parse in try/catch inside the message handler.

  • Separate protocol logic: reducers or stores should decide how messages update state; keep socket callbacks small.

  • Avoid secret exposure: do not put tokens in URLs when possible; authenticate over a secure transport or send credentials after the socket opens if your protocol allows it.

61

Which approaches are commonly used to integrate a React client-side application with a backend API server?

Integrating a React client with a backend API is usually done through data fetching from browser-based components, dedicated server-state libraries, or framework-native server rendering and mutation features. The choice depends on whether the app is a plain SPA, needs sophisticated caching, or is built with a full-stack framework such as Next.js.

Read full answer

Applies toReact 19 with function components and hooks

Client-Side HTTP Requests

  • Where components run: In a plain SPA, for example one scaffolded with Vite, components render in the browser and call backend endpoints with fetch or axios.

  • Triggering requests: Requests are typically started inside useEffect so they run after mount or when dependencies such as route parameters change.

  • Control and cost: This gives full control over headers, authorization tokens, and request bodies, but the developer must write loading, error, caching, and cancellation logic manually.

  • Cleanup: Return an AbortController cleanup function from useEffect to cancel in-flight requests and avoid state updates after unmount or dependency changes.

  • When to use: Best for small screens, simple forms, or apps where adding a third-party state library is not justified.

Server-State Libraries

  • Core idea: Libraries such as TanStack Query separate server state from UI state through hooks like useQuery and useMutation.

  • Features: They handle caching, deduplication, background refetching, stale-while-revalidate, retries, and pagination out of the box.

  • Trade-off: This reduces boilerplate and keeps UI code focused on rendering, at the cost of learning a new API and adding a dependency.

  • When to use: Recommended for most production dashboards, list/detail views, and any screen where consistency, offline behavior, or performance matters.

Framework-Native Integration

  • Server rendering: In Next.js App Router, React Server Components can fetch data directly on the server before sending HTML to the client.

  • Mutations: Server Actions let server components submit mutations without writing explicit API routes, reducing round trips and keeping sensitive logic server-side.

  • Requirement: These features require a framework that supports React Server Components; they are not available in a plain React SPA or in client components unless properly configured.

  • When to use: Use this when the project already uses Next.js and you want better SEO, faster initial loads, or tighter client-server coupling.

Comparison of Approaches

Approach Control Boilerplate Caching Framework requirement
Client-side fetch / axios Highest High Manual None
TanStack Query Medium Low Automatic None
Next.js RSC / Server Actions Server-side only Low Framework-managed Next.js App Router

Security and Error Handling

  • Data validation: Treat all API responses as untrusted; validate shapes and sanitize values on the client, but rely on the backend for authorization and injection prevention.

  • Authentication storage: Prefer HTTP-only, secure cookies for session tokens over localStorage to reduce XSS exposure.

  • Transport and origin: Enforce CORS on the backend so only trusted origins can call the API, and require HTTPS in production.

  • Error UX: Show loading states and use React error boundaries or per-request error UI so network failures do not crash the app.

  • Secrets: Avoid putting secrets, API keys, or credentials in client-side code or repository history.

62

What are the essential strategies and techniques for enhancing the runtime efficiency and initial load speed of a React application when preparing it for a production deployment?

Optimizing a React application for production requires a combination of build-time configuration and runtime code adjustments to reduce bundle size and minimize rendering work. The most effective approach combines code splitting with strategic memoization to ensure fast initial loads and smooth user interactions. This process relies on modern tooling rather than legacy scaffolding like Create React App.

Read full answer

Applies toReact 19 with function components and hooks

Build Process and Bundling

Modern bundlers such as Vite and frameworks such as Next.js handle most production optimizations automatically. A production build minifies JavaScript, removes development-only warnings and checks, and tree-shakes unused exports so dead code never reaches the browser. You should avoid Create React App because it is deprecated and lacks the performance features of current tooling. Enable gzip or Brotli compression on the web server to shrink static asset transfers, and serve assets over HTTP/2 or HTTP/3 to improve parallel delivery and reduce latency.

Preventing Unnecessary Re-renders

In React 19, function components re-render when parent state changes unless they are optimized. Apply memoization only where profiling shows a real bottleneck, because excessive memoization adds complexity and can slow the app.

  • React.memo: Wraps a component and performs a shallow prop comparison, skipping re-render when props are unchanged.

  • useMemo: Caches expensive derived values so they are recomputed only when dependencies change.

  • useCallback: Keeps function references stable across renders, preventing child components from receiving new props on every render.

  • Complete dependency arrays: Incomplete arrays cause stale closures or missed updates; exhaustive dependencies are required for correctness.

  • Primitive props: Numbers, strings, and booleans are cheap to compare and often do not need memoization, unlike objects or functions.

Hook / API Best used for Common pitfall
React.memo Components with stable props Wrapping every component adds overhead without benefit
useMemo Heavy calculations or derived data Memoizing trivial values wastes memory
useCallback Event handlers passed to memoized children Creating callbacks that are never passed down is unnecessary

Code Splitting and Lazy Loading

Load heavy code only when the user needs it to shrink the initial bundle. Use React.lazy with Suspense to dynamically import modules, which defers non-critical routes, dialogs, or heavy visualizations until interaction. Route-level splitting usually delivers a larger initial-load benefit than component-level splitting in single-page applications. Frameworks such as Next.js also provide automatic route-based splitting and prefetching for linked pages.

Server Components and Framework Features

React Server Components let rendering logic run on the server, which reduces the client-side JavaScript payload and speeds hydration. This capability requires a framework that supports it, such as the Next.js App Router, and is not available in standalone client-only React projects. Server actions can reduce network round-trips by mutating data directly on the server layer, shifting work away from low-power mobile devices.

Profiling and Measurement

Optimization must be data-driven. Use the React Developer Tools Profiler to identify slow renders and oversized component trees. Measure Core Web Vitals in production with real user monitoring, focusing on Largest Contentful Paint, Interaction to Next Paint, and Cumulative Layout Shift. Fix actual bottlenecks rather than guessing, because premature optimization often increases complexity without measurable gains. Also monitor memory and long-running sessions in browser performance tools to catch leaks that degrade runtime efficiency over time.

63

How do service workers work as a web platform feature, and what advantages can they bring to a React application?

Service workers are browser-level background scripts that can intercept network requests, cache responses, and manage offline behavior for a web app. In a React application, they can make repeat visits faster, keep the app usable when the network is unavailable, and enable progressive web app (PWA) features such as install prompts and push notifications without changing how React components render.

Read full answer

Applies tobrowsers with the Service Worker API (Chrome, Edge, Firefox, and Safari), regardless of whether the React app uses React 19 or an older version

What a Service Worker Is

  • Background script: a service worker is a JavaScript file registered by the browser and run in its own worker context, separate from the React page and without access to the DOM.

  • Network proxy: once a page is controlled, the service worker can intercept network requests and respond from CacheStorage, from the network, or from a stored fallback.

  • Scoped controller: it only controls pages and assets inside the scope defined during registration, usually the app directory or root.

  • Secure context requirement: service workers are available only on HTTPS pages or localhost during development.

How They Benefit a React Application

  • Faster repeat loads: React SPAs often download a large JavaScript bundle on first load. Caching the app shell, JS chunks, CSS, and images can reduce repeat visits to local or nearby responses.

  • Offline resilience: A cached route or fallback screen can keep a React app readable when the browser is offline, instead of showing the browser’s standard error page.

  • Better perceived performance: Strategies such as stale-while-revalidate let the app render cached content immediately, then update in the background.

  • PWA features: A service worker can enable install prompts where supported, background refresh patterns, notifications, and push messaging, subject to browser support and user permission.

  • Reduced bandwidth usage: Static React assets can be cached with content-hashed filenames, while API responses can be cached selectively.

Common Patterns for React Apps

  • App-shell caching: Cache the HTML entry point and immutable build assets such as hashed JS and CSS so a reload can be served from cache.

  • Route fallback: For SPA route changes, return a cached HTML page and let React render a lightweight offline state.

  • API caching: Use network-first for frequently changing data and cache-first for stable data. Need careful cache keys to avoid showing stale user-specific data.

  • Hydration caution: If the app is server-rendered, caching HTML may serve content that no longer matches the React server output, so cache rules should account for dynamic HTML and versioning.

  • Framework integration: React itself does not automatically register a service worker. Vite-based React apps can add one manually, and frameworks such as Next.js can generate or integrate a service worker through PWA tooling.

Limitations and Pitfalls

  • They are not a React feature: Service workers work at the browser level, so React 19 does not add new service-worker behavior.

  • First-load cost: Service workers do not help the very first load unless the app is already installed or assets are prefetched elsewhere.

  • Stale content risk: Incorrect caching can keep users on outdated bundles, APIs, or feature flags.

  • Update lifecycle complexity: Service workers have installation, waiting, activation, and client-control states, so updating them must be planned deliberately.

  • Privacy and consent: Push notifications and background features require permission, and cached data can remain on the user’s device.

Best Practices

  • Cache static, versioned assets first before runtime API caching.

  • Use unique cache names per app version and delete old caches during activation.

  • Keep the service worker small and test it with network disabled.

  • Avoid caching responses that contain secrets, session tokens, or user-specific data unless the caching rules are explicit and safe.

  • Treat service-worker behavior as a separate deployment concern and verify it on real mobile browsers, since browser policies and supported APIs can vary.

64

What is the procedure to enable secure transport layer connections for a React application during both local development and live deployment?

React does not terminate TLS itself; secure transport is provided by the development server during local work and by the production hosting infrastructure (reverse proxy or edge platform) after deployment. In React 19 projects you configure HTTPS in the tooling that serves the bundle, not in React components.

Read full answer

Applies toReact 19 with Vite 6+ or Next.js 15+ (current stable series)

Understanding the Responsibility Boundary

The React library renders UI and does not include an HTTP server, so TLS configuration belongs to the tooling that serves the assets. - Development: The dev server (Vite or Next.js CLI) listens for requests and can be configured for HTTPS. - Production: A web server such as Nginx or Caddy, or an edge platform such as Vercel or Cloudflare, terminates TLS before forwarding traffic to your application container.

Configuring HTTPS in Development with Vite

In vite.config.ts, set server.https to enable TLS. You have two common options: - Self-signed certificate: Set https: true. The connection is encrypted, but browsers show a trust warning until the certificate is accepted. - Locally trusted certificate: Generate a certificate with mkcert (mkcert -install && mkcert localhost) and pass its key and cert files. Browsers trust this once the local CA is installed.

Production Deployment Strategies

Most modern platforms handle TLS automatically, but self-hosted targets require manual configuration. - Managed Platforms: Vercel, Netlify, and Cloudflare Pages enable HTTPS by default for custom domains using Let’s Encrypt or managed PKI. - Self-Hosted: Configure Nginx or Caddy to listen on port 443 and proxy requests to your Node.js server or static asset directory. - Framework Support: Next.js can run a custom server, but TLS termination should still occur at the edge or proxy rather than inside the Node.js process.

Code Example

The following Vite configuration enables HTTPS using certificate paths supplied through environment variables, with local defaults for development.

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { readFileSync } from 'node:fs';

// Prefer environment variables so private keys are never committed to the repo.
const certPath = process.env.LOCAL_CERT_PATH || './ssl/local-cert.pem';
const keyPath = process.env.LOCAL_KEY_PATH || './ssl/local-key.pem';

export default defineConfig({
  plugins: [react()],
  server: {
    https: {
      // Load the locally trusted certificate and key at startup.
      cert: readFileSync(certPath),
      key: readFileSync(keyPath),
    },
    port: 3000,
  },
});

Security Best Practices

Cryptographic material must be handled carefully in every environment. - Never Commit Keys: Keep private keys and certificates out of version control using .gitignore. - Use Environment Variables: Inject certificate paths or secrets through environment variables rather than hard-coding them. - Rotate and Renew Certificates: Use automated renewal (for example, Let’s Encrypt with Caddy or Nginx) to avoid outages from expired certificates. - Enforce HTTPS in Production: Redirect HTTP to HTTPS and set HSTS headers at the proxy or edge layer.

Common Pitfalls

  • Browser Warnings: Self-signed certificates trigger security warnings; use mkcert or manually trust the local CA.

  • Mixed Content: Loading HTTP resources from an HTTPS page fails in modern browsers; use relative paths or HTTPS endpoints for API calls.

  • Missing Files: The dev server fails to start if certificate paths are invalid, so verify paths before running vite.

  • Framework Differences: Next.js development HTTPS may require HTTPS=1 or a custom server; consult the version-specific CLI docs rather than assuming Vite-style config.

65

What are the key reasons why prioritizing accessibility is essential when building web applications?

Accessibility ensures that web applications are usable by everyone, including people with disabilities, making inclusive design a fundamental requirement rather than an afterthought. In React 19, this means leveraging semantic HTML, proper ARIA attributes, and keyboard navigation support to meet legal standards and expand your user base significantly.

Read full answer

Applies toReact 19

Ethical and Legal Imperatives

Building accessible applications is ethically right because it prevents excluding users based on ability. Legally, many regions enforce standards like WCAG 2.1 or ADA compliance; failing to meet these can result in lawsuits and reputational damage. Treating accessibility as a core requirement from the start avoids costly retrofitting later in the development lifecycle.

Business and Usability Benefits

Accessible sites often rank better in search engines because semantic HTML provides clearer content structure for crawlers. Additionally, features designed for accessibility, such as captions or keyboard navigation, improve the experience for all users, including those on mobile devices or slow connections. Expanding accessibility opens your product to a larger market segment, including the over one billion people globally living with disabilities, which drives revenue and brand loyalty.

Implementing Accessibility in React

React encourages the use of semantic HTML elements like <button>, <nav>, and <main> instead of generic <div> tags. When custom components are necessary, ARIA attributes such as aria-label and role provide context to screen readers. In React 19, you can pass these props directly to elements, ensuring assistive technologies interpret the UI correctly without needing legacy wrappers or complex abstractions.

Managing Focus and Keyboard Interaction

Interactive components must be navigable via keyboard to support users who cannot use a mouse. Using the useRef hook allows you to manage focus programmatically, while useEffect can set initial focus when a modal opens. Ensure all interactive elements respond to Enter and Space keys, and visible focus indicators are never removed via CSS without replacement.

Best Practices and Common Pitfalls

Always test with screen readers like NVDA or VoiceOver, not just automated linters which miss context. Avoid using onClick on <div> elements without adding role="button" and keyboard handlers, as this breaks navigation for assistive tech. Remember that React Server Components require a framework like Next.js App Router, and accessibility attributes must still be rendered to the client DOM. Never hide focus outlines entirely; use :focus-visible to style them appropriately for mouse versus keyboard users to maintain usability.

66

What practices and tools should you implement to ensure a React application is accessible to all users?

Ensuring a React application is accessible requires integrating semantic markup, proper ARIA attributes, and robust keyboard support into every component. The foundational goal is accessibility, which guarantees that users relying on screen readers or keyboard navigation can interact with your interface just as effectively as mouse users. This is achieved through deliberate component design, automated linting, and manual testing with assistive technologies.

Read full answer

Applies toReact 19

Semantic HTML and ARIA Attributes

Prefer native HTML elements (<button>, <nav>, <main>) over styled <div> elements because they come with built-in accessibility behaviors. For example, use <button> for actions and <a> for navigation, never a <div> with an onClick handler. When custom components are necessary, use ARIA roles and properties to convey state and structure to assistive technologies. - role: Defines the element type (e.g., role="button"). - aria-label: Provides an accessible name when visible text is insufficient. - aria-expanded: Indicates whether a collapsible section is open or closed. - aria-describedby: Links an element to a longer description elsewhere on the page.

Keyboard Navigation and Focus Management

All interactive elements must be reachable and operable via the keyboard, typically using the Tab key. Manage focus programmatically when the DOM changes dynamically, such as opening a modal or navigating between pages. - Use useRef to store references to DOM nodes that need focus. - Trigger .focus() inside a useEffect hook when the relevant state changes. - Ensure focus traps are implemented within modals to prevent users from tabbing out unintentionally. - Return focus to the element that opened a dialog once it closes.

Testing and Linting Tooling

Automated tools catch common issues early in the development cycle, reducing the burden of manual audits later. Integrate these tools into your build pipeline and editor configuration, and run them in continuous integration to block regressions before deployment.

Tool Purpose Integration
eslint-plugin-jsx-a11y Static analysis rules for JSX that flag missing alt text or incorrect role usage ESLint config in editor and CI
axe-core Runtime testing engine used by browser extensions and CI pipelines Browser extension, Jest, Cypress
Lighthouse Audits performance, accessibility, and SEO in Chrome DevTools CI via Lighthouse CI, manual runs
  • Screen Readers: Manual testing with NVDA (Windows), VoiceOver (macOS/iOS), or TalkBack (Android).

Color, Contrast, and Visual Design

Avoid relying solely on color to convey information, as this excludes users with color vision deficiencies. Do not remove focus outlines via CSS (outline: none) without providing a visible alternative indicator. - Contrast Ratios: Ensure text meets WCAG AA standards (minimum 4.5:1 for normal text, 3:1 for large text). - Focus Indicators: Provide a clear visible focus ring, such as a 2px solid outline with high contrast. - Resizable Text: Support browser zoom up to 200% without loss of content or functionality.

Common Pitfalls

  • Missing Alt Text: Always provide descriptive alt attributes for informative images; use alt="" for decorative images.

  • Dynamic Content: Announce live region changes using aria-live for status updates.

  • Form Labels: Associate every <input> with a <label> using htmlFor/id or wrap the input inside the label.

  • Landmark Roles: Use <header>, <nav>, <main>, <aside>, and <footer> to give screen readers page structure.

67

What does the ARIA specification define and what is the correct approach for applying its attributes within React components to ensure accessibility?

ARIA (Accessible Rich Internet Applications) is a W3C technical specification that defines roles, states, and properties so dynamic content and complex UI controls can be exposed to assistive technologies such as screen readers. In React, the correct approach is to pass standard ARIA attributes—role, aria-*, and related HTML accessibility attributes—directly onto JSX elements, letting React render them unchanged into the DOM.

Read full answer

Applies toReact 19 with function components and hooks (also React 18+)

How ARIA Works in React

React DOM elements mirror HTML syntax. Unlike many DOM properties that React camelCases (className, tabIndex), ARIA attributes keep their lowercase, hyphenated HTML names: aria-label, aria-expanded, aria-labelledby, and role. React passes these attributes through to the underlying DOM node without transformation, so browsers and assistive technologies read them exactly as the specification requires. Values can be strings, booleans, or undefined; React serializes booleans to "true"/"false" and omits the attribute entirely when the value is undefined.

Key ARIA Categories

  • Roles: Describe what an element is, such as role="button" or role="dialog". Prefer native semantic elements (<button>, <nav>, <main>) because they already supply the correct role and keyboard behavior.

  • States: Describe conditions that change frequently, such as aria-expanded="true" or aria-checked="false". Bind these to React state so they update on every render.

  • Properties: Describe relationships or characteristics, such as aria-labelledby and aria-describedby, which associate a control with visible text elsewhere in the DOM.

Code Example

This toggle button shows ARIA attributes driven by state and a unique ID generated with useId.

import { useState, useId } from 'react';

export default function MenuToggle() {
  const [isOpen, setIsOpen] = useState(false);
  // Generates a stable ID that is unique across the page.
  const panelId = useId();

  return (
    <>
      <button
        aria-controls={panelId}
        aria-expanded={isOpen}
        onClick={() => setIsOpen(!isOpen)}
        type="button"
      >
        {isOpen ? 'Close' : 'Open'} Menu
      </button>
      <div id={panelId} hidden={!isOpen}>
        {/* Menu items */}
      </div>
    </>
  );
}

Best Practices and Common Pitfalls

  • Prefer Semantic HTML: A native <button> already has the correct role, focus style, and keyboard activation. Adding role="button" to a <div> forces you to reimplement keyboard support manually.

  • Generate Stable IDs with useId: Attributes like aria-controls, aria-labelledby, and aria-describedby reference other elements by ID. Use useId instead of hardcoded strings so repeated component instances remain valid.

  • Handle Booleans and undefined Correctly: Pass boolean state directly (aria-expanded={isOpen}) rather than building strings. Set a value to undefined when the attribute should not be present.

  • Avoid Redundancy: Do not add aria-label to an element whose visible text already describes it clearly. Use aria-label only when the visible label is ambiguous or insufficient for screen readers.

  • Keep ARIA in Sync with UI: Because React re-renders from state, ARIA attributes usually stay correct automatically. Avoid imperative DOM mutations that bypass React’s render path.

  • Test Accessibility: Combine automated linting (eslint-plugin-jsx-a11y) with manual screen-reader testing (NVDA on Windows, VoiceOver on macOS/iOS) and, where possible, axe or Lighthouse audits.

When Not to Use ARIA

If a standard HTML element already provides the needed semantics and behavior, do not replace or duplicate it with ARIA. For example, use <nav> instead of <div role="navigation">, and use <button> instead of <div role="button">. Overusing ARIA can create conflicting semantics, hide real elements, or make keyboard navigation harder. ARIA should fill gaps where HTML semantics are insufficient, not replace them.

68

What does internationalization mean in the context of React development?

Internationalization (i18n) is the process of designing software so it can support multiple languages, regions, and cultural conventions without requiring engineering changes. In React, this means building applications that can dynamically display content in different languages and adapt formats for date, number, currency, and other locale-specific values.

Read full answer

Applies toReact 19 with third-party i18n libraries

What Internationalization Enables

  • Display text in multiple languages (English, Spanish, Japanese, etc.)

  • Adapt date, time, and number formats based on region

  • Handle right-to-left text layouts for Arabic or Hebrew

  • Support pluralization rules that vary across languages

  • Manage locale-aware sorting and string comparison

Core Components of i18n

To implement internationalization in a React application, you need several key components:

  • Translation management: A system to store translations organized by language

  • Locale detection: Automatically detect or let users select their preferred language

  • Message extraction: Tools to find translatable strings in code

  • Pluralization: Rules for handling singular vs. plural forms per language

  • Formatting functions: Utilities for locale-aware numbers, dates, and currencies

  • Dynamic switching: Ability to change language at runtime without page reload

Popular Libraries

Library Approach Strengths
react-i18next Built on i18next Mature, TypeScript support, SSR compatible, handles namespaces
FormatJS / react-intl Built on formatjs Strong formatting API, good browser support
react-polyglot Built on node-polyglot Simple, lightweight, deprecated in favor of react-i18next

For new projects, react-i18next with i18next is the most commonly used and well-documented solution.

How It Works

  1. Install the library using npm, yarn, or pnpm (e.g., npm install i18next react-i18next)

  2. Initialize the i18n configuration with available languages and resources

  3. Provide translations in JSON files or programmatically loaded resources

  4. Wrap your app with the I18nextProvider component from react-i18next

  5. Access translations using hooks (useTranslation) within functional components

  6. Handle formatting for dates, numbers, and currencies through integration options or additional packages like intl-messageformat

Best Practices

  • Store translation keys in separate JSON files organized by language

  • Use namespaces for large projects to split resources logically

  • Enable translation loading progressively rather than all at once

  • Keep UI text in translation files, never hardcoded in JSX

  • Provide meaningful fallback translations when missing

  • Test RTL layouts if supporting right-to-left languages

  • Validate string interpolation and placeholder safety

Common Pitfalls

  • Hardcoding strings: Text inside components will not translate

  • Ignoring pluralization: English single/plural doesn’t apply globally

  • Overlooking length differences: Translated text often expands or shrinks

  • Missing date/locale formats: Hardcoded format strings break internationally

  • Not tracking unused keys: Translation files grow unmaintained without audits

Server-Side Rendering Considerations

When using frameworks like Next.js App Router that support server components and server actions, i18n requires special handling because translations must be resolved on the server side before rendering. Consider:

  • Preloading translations for each route

  • Using dynamic imports with ssr: false when needed

  • Managing locale detection before rendering

Platform Support

The above approach works consistently across: - Web browsers: All modern browsers supported - Mobile (via React Native): Same libraries work with minor adjustments - Server-side rendering: Compatible with Next.js, Remix, and other SSR frameworks

No operating system differences affect client-side React i18n implementation.

70

What practices and tooling would you implement to guarantee high code quality and long-term maintainability in a modern React application?

Ensuring quality relies on a comprehensive tooling strategy that enforces standards automatically rather than relying on manual review alone. This combines static analysis, strict typing, and behavioral testing to catch errors early in the development cycle, reducing technical debt over time.

Read full answer

Applies toReact 19 with TypeScript 5.x and Vite

Automated Linting and Formatting

Configure ESLint with eslint-plugin-react and eslint-plugin-react-hooks to enforce the Rules of Hooks and detect common mistakes. Pair this with Prettier for consistent formatting, ensuring diffs remain readable across the team. Use ESLint’s flat config system (eslint.config.js), which became the required format in ESLint v9 for better performance and simpler configuration management, reducing lint startup time significantly.

Type Safety with TypeScript

Use TypeScript with strict mode enabled to define props, state, and event handlers explicitly. This prevents runtime errors caused by undefined values or incorrect prop types passed between components. Enable noUncheckedIndexedAccess to avoid accidental undefined access on arrays. When pairing TypeScript with React, ensure versions align properly: React 19 typically works with TypeScript 5.0+.

Testing Philosophy

Adopt React Testing Library to test components based on user behavior rather than implementation details. Focus on querying elements by accessible roles, labels, or text content using getByRole or getByLabelText. Simulate interactions with userEvent rather than fireEvent for realistic browser behavior. Mock external APIs at the network level using tools like MSW rather than mocking component internals, which keeps tests resilient to refactoring. Popular test runners include Vitest or Jest. Integrate these tests into CI pipelines to block merges when coverage thresholds drop.

Component Architecture and Hooks

Prefer function components and custom hooks over class components, which are legacy. Extract side effects into reusable hooks to keep components declarative and logic testable. Avoid useEffect for derivable data; compute values during render instead to reduce complexity. Composition should be favored over inheritance to share logic across unrelated components, allowing hooks to be shared without hierarchy constraints. Apply React.memo or useMemo only after profiling identifies performance bottlenecks, as premature optimization often harms readability. For larger applications, distinguish between Server Components (for data fetching and rendering on the server) and Client Components (for interactivity).

Common Pitfalls and Best Practices

Do not use Create React App for new projects as it is deprecated; use Vite or a framework like Next.js. Ensure every useEffect dependency array is exhaustive to prevent stale closures and infinite loops. Avoid storing derived state in useState when it can be computed directly from props or existing state during render. Regularly review dependency arrays during code reviews to catch missing dependencies early.

  • Framework choice: Next.js App Router supports Server Actions and React Server Components for optimized data fetching

  • Build tool: Vite provides faster development experience than older webpack-based setups

  • Package manager: Use npm, yarn, pnpm, or bun consistently across the team

  • CI/CD: Automate linting, type checking, and testing in pull requests before merging

71

What approach would you take when receiving a new feature request or bug report during active development of a React application?

This describes a structured issue management process that ensures bugs and features are handled systematically without disrupting ongoing development.

Read full answer

Applies toReact 19 with any modern React framework (Next.js, Vite, Remix)

Initial Triage and Validation

When a feature request or bug report arrives, validate and categorize it before scheduling work:

  • Verify the bug: Reproduce it using exact steps, browser, device, and app version

  • Document reproduction: Share a minimal sandbox or reduced test case so others can confirm it quickly

  • Assess severity: Classify impact as critical, high, medium, or low based on affected users and blocked functionality

  • Check for duplicates: Search the issue tracker to avoid redundant effort

Critical issues such as security vulnerabilities, data loss, or total app failure should bypass normal scheduling. Low-priority items can enter regular backlog refinement.

Impact Analysis and Planning

Before implementation, understand the change’s reach:

  • Scope evaluation: Estimate effort, required files, and possible side effects

  • Dependencies: Identify affected components, APIs, shared state, or third-party services

  • Timeline fit: Decide whether the work fits the current sprint or must be deferred

  • Risk assessment: Flag performance regressions, breaking changes, or accessibility impacts

For React 19 projects, also check whether the change affects:

  • Component state management patterns

  • useEffect dependencies and cleanup logic

  • Server Component versus Client Component boundaries

  • Bundle size, code splitting, or loading performance

Implementation Strategy

Follow a predictable workflow while developing:

  1. Create a dedicated branch from main with a descriptive name, such as feature/user-auth-improvement or bugfix/login-crash-fix

  2. Write tests first that cover the bug scenario or feature acceptance criteria

  3. Implement incrementally using small, reviewable commits rather than one large change

  4. Update documentation for new props, behavior changes, or public APIs

  5. Run the full test suite before pushing to catch regressions

If the issue involves rendering failures, use React error boundaries—class components or framework-provided utilities—rather than ad-hoc global window event listeners. Keep effect dependency arrays honest to avoid stale closures.

Code Review and QA

Before merging:

  • Peer review: Require at least one reviewer unfamiliar with the change

  • Performance check: Use React DevTools Profiler to detect unnecessary renders

  • Accessibility audit: Confirm keyboard navigation and screen reader support are preserved

  • Cross-browser testing: Verify behavior across supported browsers and devices

  • E2E validation: Run end-to-end tests for critical user flows

Deployment and Monitoring

After merge and release:

  • Monitor analytics: Track error rates, Core Web Vitals, and adoption metrics

  • Gather feedback: Confirm with stakeholders that the bug is fixed or the feature delivers value

  • Document lessons learned: Record root cause and fix notes in the team wiki

  • Prevent recurrence: Add regression tests or lint rules where appropriate

Issue Type Response Time Testing Required Review Level
Critical Bug Immediate Full regression All seniors
High Priority Next sprint Unit + E2E Minimum 1 reviewer
Feature Request Backlog planning As scoped Team consensus
Minor Fix Next available slot Targeted unit Any available

Common Pitfalls to Avoid

  • Skipping tests: Increases regression risk and makes future refactors unsafe

  • Ignoring effect dependencies: Stale closures or missing dependencies can introduce subtle bugs

  • Not updating types: TypeScript interfaces and props should reflect new behavior

  • Large PRs: Combining many changes makes review slow and error-prone

  • Forgotten rollback plan: Always know how to revert a release if it causes production issues

Following this structured approach maintains quality, protects team velocity, and prevents new issues from being introduced alongside fixes.

72

How would you approach performance tuning for a React component handling intricate state interactions alongside multiple nested child components?

Optimizing a React component requires starting with performance profiling to identify bottlenecks before applying memoization or state restructuring. You should measure render times and state updates using React Developer Tools rather than guessing which parts are slow. Once identified, apply targeted optimizations like state colocation and memoization only where necessary.

Read full answer

Applies toReact 18+ with function components and hooks

Profile Before Optimizing

Begin by recording component interactions with the React Developer Tools Profiler. Look for components that re-render when unrelated state changes occur. High-frequency renders without DOM mutations are often harmless, but expensive calculations or deep component trees warrant attention. Profiling ensures you solve actual performance problems rather than perceived ones, avoiding premature optimization.

Refactor State Management

Complex state logic often benefits from useReducer instead of multiple useState calls, as it centralizes update logic and reduces prop drilling. Keep state as close to where it is needed as possible; lifting state too high causes unnecessary re-renders in sibling components. Avoid storing derived data in state—compute it during render or with useMemo to prevent synchronization bugs and reduce memory usage.

Memoization and Stability

Wrap pure child components in memo to prevent re-renders when props remain shallowly equal. Pass stable references to these children using useCallback for event handlers and useMemo for objects or arrays. These tools help ensure predictable performance while maintaining code clarity.

Code Example

import { memo, useMemo, useCallback, useState } from 'react';

const ChildComponent = memo(({ data, onUpdate }: { data: string; onUpdate: () => void }) => {
  return <div onClick={onUpdate}>{data}</div>;
});

export const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // Stable reference prevents unnecessary re-renders of memoized children
  const handleUpdate = useCallback(() => {
    setText('updated');
  }, []);

  // Memoize derived data to avoid recalculating on every render
  const processedData = useMemo(() => text.toUpperCase(), [text]);

  return (
    <div>
      <p>Count: {count}</p>
      {/* Only re-renders when processedData or handleUpdate changes */}
      <ChildComponent data={processedData} onUpdate={handleUpdate} />
      <button onClick={() => setCount(c => c + 1)}>Increment Count</button>
    </div>
  );
};

Common Pitfalls

Avoid creating inline objects or functions in JSX props, as this breaks shallow equality checks used by memo. Do not wrap every component in memo by default, as the comparison overhead can outweigh benefits for simple components. Ensure all dependencies in useEffect, useMemo, and useCallback are correctly listed to prevent stale data or infinite loops. Finally, never optimize before measuring; unoptimized code is often fast enough for typical user interfaces.

Sign in to keep reading

You've opened 2 full answers. Sign in to read all 72 React answers, mark what you know and pick up where you left off.

All Web and Mobile Development subjects