React Flashcards

Also Next (75 cards)

1
Q

Describe the pros and cons of using React.js

A

React is powerful for building UIs but needs good state management and setup!

Pros:
- Component-Based → Reusable, modular UI development.
- Virtual DOM → Faster updates & better performance.
- One-Way Data Binding → Predictable state management.
- Hooks & Functional Components → Cleaner and more efficient code.
- Large Ecosystem → Rich libraries, strong community support.

Cons:
- Steep Learning Curve → JSX, hooks, and state management require time to master.
- Boilerplate Code → Requires setup with Webpack, Babel, etc.
- Frequent Updates → Breaking changes and new APIs can require adjustments.
- SEO Challenges → Requires SSR (Next.js) for better search engine optimization.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How to Implement SSR in React

A

Server-Side Rendering (SSR) in React generates HTML on the server instead of the client for faster load times and better SEO.

Server Components reduce client-side JavaScript and improve performance.

Using Next.js (Best Approach)
- By default, components in app/ are Server Components.
- Fetch data directly inside components (no need for useEffect).

Server Components reduce client-side JavaScript and improve performance.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How to improving web page loading performance, and how to diagnose it

A
  1. How to Improve Performance:
    Minimize & Optimize Assets
    - Use image compression (WebP, AVIF).
    - Minify CSS, JS, and HTML.
    - Use lazy loading (loading=”lazy”) for images and iframes.

Optimize JavaScript
- Use code splitting (React.lazy(), dynamic imports).
- Defer non-critical JS (defer, async).
- Reduce unused JavaScript (tree shaking).

Improve Server & Network Efficiency
- Use a Content Delivery Network (CDN).
- Enable Gzip/Brotli compression.
- Implement Server-Side Rendering (SSR) or Static Generation (SSG).
- Use caching strategies (Cache-Control, ETags).

Reduce Render-Blocking Resources
- Optimize CSS & font loading (preload, font-display: swap).
- Minimize render-blocking scripts (async, defer).

  1. How to Diagnose Performance Issues
    Google Lighthouse (Chrome DevTools)
    - check Performance, Accessibility, Best Practices, SEO.

PageSpeed Insights (Google)
- Analyzes speed on mobile & desktop.

Web Vitals Metrics
- Largest Contentful Paint (LCP) → Measures loading speed.
- First Input Delay (FID) → Measures interactivity.
- Cumulative Layout Shift (CLS) → Measures visual stability.

Chrome Performance Tab
- Record a performance profile to detect slow scripts, excessive reflows, or blocking resources.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

How does the virtual DOM work in React, and why is it important?

A

The virtual DOM (VDOM) in React is a lightweight copy of the actual DOM. When a component’s state changes, React updates the VDOM first, compares it to the previous version (diffing), and then updates only the changed parts in the real DOM (reconciliation).

Why it matters:
- Faster updates – Minimizes direct DOM manipulations.
- Efficient rendering – Updates only changed elements.
- Improved performance – Reduces reflows and repaints.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

What are React Hooks, and how do they differ from class lifecycle methods?

A

React Hooks are functions that let you use state and lifecycle features in functional components.

Key Differences from Class Lifecycle Methods:
- Simpler & Cleaner – No need for class components.
- More Composable – Combine logic with custom hooks.
- Runs Per Render – Unlike lifecycle methods, hooks re-run with every render.

Examples:
- useState – Manages state.
- useEffect – Handles side effects (like componentDidMount, componentDidUpdate, componentWillUnmount).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Explain the concept of Higher-Order Components (HOCs) and provide use cases.

A

Higher-Order Components (HOCs) are functions that take a component and return an enhanced version of it. They allow reuse of component logic without modifying the original component.

Use Cases:
- Code Reusability – Share logic across multiple components.
- Authorization Handling – Wrap components to check user roles.
- Fetching Data – Attach API fetching logic to components.

Examples:

const withAuth = (Component) => (props) => {
  return isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />;
};

Usage:

const ProtectedPage = withAuth(PageComponent);
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What is Context API, and how does it help in managing global state?

A

Context API is a built-in React feature that allows global state management without prop drilling. It provides a way to share data (like themes, authentication, or settings) across components without passing props manually at every level.

How it Works:
1. Create a Context – const MyContext = React.createContext().
2. Provide the State – Wrap components with MyContext.Provider.
3. Consume the State – Use useContext(MyContext) in child components.

Why Use It?
- Avoids prop drilling – No need to pass props deep in the component tree.
- Simpler than Redux – Ideal for small to medium state management needs.
- Better Performance – Prevents unnecessary re-renders with proper optimization.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

How does React’s reconciliation algorithm work?

A

React’s reconciliation algorithm determines the most efficient way to update the DOM when state or props change.

How It Works:
1. Virtual DOM Comparison – React creates a new virtual DOM and compares it with the previous one.
2. Diffing Algorithm – It finds changes using a key-based heuristic:
- Same type elements → Updates attributes.
- Different type elements → Replaces the old element.
- Lists with keys → Moves, adds, or removes items efficiently.
3. Batching & Commit Phase – Only necessary updates are applied to the real DOM.

Why It’s Efficient?
- Minimizes direct DOM updates, reducing performance costs.
- Uses keys in lists to optimize element reordering.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Describe the concept of “lifting state up” in React and provide an example.

A

In React, lifting state up means moving shared state to a common ancestor so multiple components can access and update it. This avoids duplicate state and ensures a single source of truth.

Example: Sibling Components Sharing State

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Counter count={count} />
      <Button setCount={setCount} />
    </div>
  );
}

function Counter({ count }) {
  return <h1>Count: {count}</h1>;
}

function Button({ setCount }) {
  return <button onClick={() => setCount(prev => prev + 1)}>Increment</button>;

Why It’s Useful?
- Keeps state in sync across components.
- Prevents unnecessary duplication of state.
- Makes components more reusable and modular.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What is the purpose of the useReducer hook, and how does it compare to useState?

A

Purpose of useReducer

The useReducer hook manages complex state logic using a reducer function. It’s useful when state transitions depend on the previous state, making it a good alternative to useState for handling structured updates.

How It Compares to useState
- useState is best for simple state updates where the next value is independent of the previous state.
- useReducer is better for complex state logic, especially when multiple related state changes happen together.
- useReducer uses an action-based approach, making updates more predictable and structured.
- It also helps avoid unnecessary re-renders by batching updates efficiently.

Example: Counter with useReducer

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 (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </div>
  );
}

When to Use useReducer?

Use it when state logic is complex, involves multiple related updates, or depends on previous values (e.g., form handling, authentication, or global state management).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

How can you optimize the performance of a React application?

A

Ways to Optimize React Performance
1. Use Memoization
- React.memo() prevents unnecessary re-renders of components.
- useMemo() caches expensive calculations.
- useCallback() memoizes functions to avoid re-creating them.
2. Lazy Load Components
- Use React.lazy() and Suspense to load components only when needed.
3. Optimize Re-renders
- Avoid unnecessary state updates.
- Use the dependency array in useEffect wisely.
- Keep components as pure as possible.
4. Virtualize Long Lists
- Use libraries like react-window or react-virtualized to render only visible items in large lists.
5. Optimize React Keys
- Always use unique and stable keys in lists (avoid indexes as keys).
6. Debounce Expensive Operations
- Throttle or debounce events like search inputs to prevent excessive re-renders.
7. Minimize Bundle Size
- Remove unused dependencies.
- Use code splitting with import() for dynamic imports.
8. Use Efficient State Management
- Avoid unnecessary context re-renders.
- Use Redux, Recoil, or Zustand efficiently.
9. Optimize Images & Assets
- Use lazy loading for images.
- Compress images and use SVGs when possible.
10. Enable Server-Side Rendering (SSR) or Static Site Generation (SSG)

Use Next.js for better SEO and faster page loads.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Explain the role of keys in React lists and why they are important.

A

Role of Keys in React Lists

Keys in React help identify which items have changed, added, or removed in a list. They enable React’s reconciliation algorithm to efficiently update the DOM by minimizing unnecessary re-renders.

Why Are Keys Important?
- Optimized Rendering – Prevents unnecessary re-renders by allowing React to track items efficiently.
- Preserves Component State – Ensures React keeps track of component state when list order changes.
- Avoids UI Bugs – Without keys, React may mix up components, leading to unexpected behavior.

Best Practices for Keys
- Always use a unique and stable identifier (like an ID from a database).
- Avoid using array indexes as keys unless the list is static and never changes.

Example

const items = [{ id: 1, name: "Apple" }, { id: 2, name: "Banana" }];
return items.map((item) => <li key={item.id}>{item.name}</li>);

Incorrect (Using Index as Key)

return items.map((item, index) => <li key={index}>{item.name}</li>);

Using indexes as keys can cause incorrect reordering behavior when list items change.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What are React Portals, and when should they be used?

A

React Portals allow rendering components outside the normal React component tree, typically into a different DOM node. They help when you need a component to visually break out of its parent but still stay connected to React’s state and event system.

When to Use Portals?
- Modals & Dialogs – Prevents nesting issues and ensures proper z-index stacking.
- Tooltips & Popovers – Ensures they are not clipped by overflow: hidden or position: relative styles.
- Dropdown Menus – Avoids unwanted clipping inside parent containers.

Why Use Portals?
- Avoids CSS overflow issues in nested elements.
- Improves accessibility and UI behavior for floating elements.
- Maintains React state and event handling even when rendered outside the normal hierarchy.

How to Use Portals
1. Create a target DOM element in index.html:

<div id="portal-root"></div>
  1. Render a component into the portal:
import ReactDOM from "react-dom";

function Modal({ children }) {
  return ReactDOM.createPortal(
    <div className="modal">{children}</div>,
    document.getElementById("portal-root")
  );
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Describe the benefits and limitations of server-side rendering (SSR) with Next.js.

A

Benefits of Server-Side Rendering (SSR) in Next.js
1. Improved SEO – Pages are fully rendered on the server before reaching the browser, making it easier for search engines to index content.
2. Faster Initial Load – The browser receives a fully populated HTML page, reducing the time to First Contentful Paint (FCP).
3. Better Performance for Dynamic Content – Ideal for pages with frequently changing data, ensuring fresh content on every request.
4. Improved Social Media Sharing – Metadata (like Open Graph tags) is properly populated, enhancing link previews.
5. Direct Data Fetching – Fetches data before rendering, avoiding client-side API calls.

Limitations of SSR
1. Slower Time-to-First-Byte (TTFB) – Rendering on the server for every request adds latency compared to static pages.
2. Higher Server Load – Each request triggers a full re-render, increasing server resource consumption.
3. Complex Caching – Unlike Static Site Generation (SSG), SSR requires more caching strategies to improve performance.
4. Limited Client-Side Interactivity – While SSR delivers a fully rendered page, additional client-side hydration may be needed for interactive features.
5. More Expensive at Scale – Requires a powerful backend infrastructure, especially for high-traffic applications.

When to Use SSR?
- Pages that rely on real-time data (e.g., stock prices, news feeds).
- Authenticated pages where SEO matters.
- E-commerce product pages that need fresh inventory and price updates.

For other use cases, SSG (Static Site Generation) or ISR (Incremental Static Regeneration) may be more efficient.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

How do you implement code splitting in a React application?

A

Code splitting helps optimize performance by loading only the necessary JavaScript for a given page or component, reducing initial load times.

  1. Using React.lazy() for Component-Level Splitting

The simplest way to split code in React is by using React.lazy() with Suspense.

import React, { Suspense } from "react";

const LazyComponent = React.lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

Best for: Loading individual components dynamically.

  1. Using Dynamic Imports for Route-Level Splitting

With React Router, you can lazy-load pages only when they are visited.

import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { lazy, Suspense } from "react";

const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

Best for: Splitting page-level components.

  1. Using Webpack’s Code Splitting (import())

Webpack automatically splits code when you use dynamic import() in functions.

function loadComponent() {
  import("./HeavyComponent").then((module) => {
    const Component = module.default;
    // Use Component as needed
  });
}

Best for: Loading modules dynamically based on user actions.

  1. Leveraging Next.js Automatic Code Splitting

If using Next.js, it automatically code-splits at the page level. However, you can optimize it further:

import dynamic from "next/dynamic";

const DynamicComponent = dynamic(() => import("../components/HeavyComponent"), {
  ssr: false,
  loading: () => <p>Loading...</p>,
});

Best for: Optimizing performance in Next.js apps.

Why Use Code Splitting?
- Improves initial page load time by reducing bundle size.
- Enhances performance on slow networks by loading only necessary components.
- Reduces JavaScript execution time by avoiding unnecessary scripts.

Tip: Always test performance improvements using Lighthouse or React DevTools Profiler after implementing code splitting.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Explain the concept of controlled and uncontrolled components in form handling.

A

Controlled Components

A controlled component is a form element whose value is controlled by React state. Every change updates the state, and the state defines the input’s value.

Why Use Controlled Components?
- Predictable – The state always reflects the input value.
- Easier Validation – You can validate user input in real-time.
- Better Control – Useful for dynamic form behaviors.

Example:

function ControlledInput() {
  const [value, setValue] = useState("");

  return (
    <input
      type="text"
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

Uncontrolled Components

An uncontrolled component manages its own state. Instead of storing the value in React state, you access it via refs when needed.

Why Use Uncontrolled Components?
- Simpler for Non-Dynamic Forms – Ideal for forms where you only need values on submit.
- Better Performance – React doesn’t re-render on every keystroke.

Example:

function UncontrolledInput() {
  const inputRef = useRef();

  function handleSubmit() {
    alert(inputRef.current.value);
  }

  return (
    <>
      <input type="text" ref={inputRef} />
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

Key Differences
- Controlled Components: State-driven, React updates value (useState).
- Uncontrolled Components: DOM-driven, use refs (useRef).

Which to Use?
- Use controlled components when you need validation, dynamic behavior, or real-time updates.
- Use uncontrolled components for simple forms where you only need values on submit.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

What are custom hooks, and how can they help in reusing logic across components?

A

A custom hook is a reusable function that encapsulates stateful logic using React hooks (useState, useEffect, etc.). It helps avoid code duplication and improves code organization.

Why Use Custom Hooks?
- Reusability – Share logic across multiple components.
- Cleaner Code – Reduces clutter in components.
- Separation of Concerns – Keeps UI and logic independent.

When to Use Custom Hooks?
- Fetching data (useFetch)
- Handling form state (useForm)
- Managing local storage (useLocalStorage)
- Debouncing input values (useDebounce)
- Handling authentication (useAuth)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

How can you manage side effects in a React application?

A

Side effects in React refer to anything that affects something outside the component, such as fetching data, updating the DOM, or interacting with local storage. The best way to handle them is with the useEffect hook.

  1. Using useEffect for Side Effects

useEffect runs after the component renders and can handle:
- Fetching data
- Event listeners
- Updating the DOM
- Subscriptions (e.g., WebSockets)

Example: Fetching Data

import { useEffect, useState } from "react";

function Users() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => setUsers(data));
  }, []); // Empty dependency array -> Runs once when mounted

  return <ul>{users.map((user) => <li key={user.id}>{user.name}</li>)}</ul>;
}

Why? The empty [] ensures the effect runs only on mount.

  1. Cleaning Up Side Effects

Some effects need cleanup, like event listeners or subscriptions, to prevent memory leaks.

Example: Cleanup on Unmount

useEffect(() => {
  fetch(`https://api.example.com/data?query=${searchTerm}`)
    .then((res) => res.json())
    .then((data) => setResults(data));
}, [searchTerm]); // Runs every time `searchTerm` changes

Why? Only triggers the effect when searchTerm updates, avoiding unnecessary requests.

  1. Conditional Side Effects

You can make useEffect react to state/prop changes by including them in the dependency array.

Example: Fetching Data on State Change

useEffect(() => {
  fetch(`https://api.example.com/data?query=${searchTerm}`)
    .then((res) => res.json())
    .then((data) => setResults(data));
}, [searchTerm]); // Runs every time `searchTerm` changes

Why? Only triggers the effect when searchTerm updates, avoiding unnecessary requests.

  1. Avoiding Unnecessary Re-renders

If the effect depends on an object/array, use memoization with useCallback or useMemo.

Example: Memoized Callback

const fetchData = useCallback(() => {
  fetch("/api/data").then((res) => res.json()).then(setData);
}, []); // Memoized function

Why? Prevents function recreation on every render.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Discuss the trade-offs between using Redux and the Context API for state management.

A

Both Redux and Context API help manage global state in React, but they have different strengths and trade-offs.

When to Use Context API

  • Best for small to medium-sized applications where state is not too complex.
  • Simpler and built into React—no need for extra libraries.
  • Easy to set up—just use React.createContext() and useContext().

Example Usage:
- Theme switching (light/dark mode).
- User authentication state (logged-in user info).
- Language preferences in a multi-language app.

Limitations:
- Re-renders all components that consume the context, which can lead to performance issues.
- Not ideal for deeply nested or frequently changing state.

When to Use Redux

  • Best for large applications with complex and frequently changing state.
  • Centralized store allows better state debugging and tracking with DevTools.
  • Optimized re-renders—only the components that need updates will re-render.
  • Middleware support (Redux Thunk, Redux Saga) for handling async logic.

Example Usage:
- E-commerce cart management (adding/removing items, persisting state).
- Large-scale dashboards with multiple state updates.
- Multiplayer game states where many components need access to real-time data.

Limitations:
- More boilerplate (reducers, actions, dispatches).
- Extra dependency—requires installing redux and react-redux.
- Learning curve—concepts like reducers and middleware may be overwhelming for beginners.

Which One Should You Choose?
- If your app only needs to share simple state (theme, auth, settings), use Context API.
- If your app has complex, deeply nested, or frequently changing state, use Redux.

For medium-sized apps, consider Zustand or Recoil—they offer simpler alternatives to Redux while improving performance over Context API.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

What are fragments in React, and when should they be used?

A

React fragments (<></>) allow you to group multiple elements without adding extra DOM nodes. This helps keep the HTML structure clean and avoids unnecessary wrappers like <div>.

When to Use Fragments?
1. Avoid Unnecessary <div> Wrappers
- Helps maintain cleaner and more semantic HTML.
- Prevents unwanted styles from affecting layout due to extra divs.

  1. When Returning Multiple Elements from a Component
    - JSX requires a single root element, so fragments help return multiple elements without a wrapper div.
  2. Better Performance in Lists
    <React.Fragment key={}> allows adding keys when rendering a list without an extra wrapper.
function ItemList({ items }) {
  return items.map(item => (
    <React.Fragment key={item.id}>
      <h2>{item.name}</h2>
      <p>{item.description}</p>
    </React.Fragment>
  ));
}

Why Use Fragments?
- Removes unnecessary DOM nodes
- Improves performance
- Prevents unwanted styles or layout shifts
- Useful in lists where a key is required

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

How does React handle events differently from vanilla JavaScript?

A

React event handling is different from vanilla JavaScript in a few key ways:

  1. Uses Synthetic Events

React wraps native DOM events in Synthetic Events, which standardizes event properties across browsers.

function handleClick(event) {
  console.log(event); // SyntheticEvent, not a native event
}

<button onClick={handleClick}>Click Me</button>

Why? Synthetic Events provide a consistent API across browsers, making event handling more predictable.

  1. Uses CamelCase for Event Names

Unlike vanilla JS (onclick), React uses camelCase (onClick).

React Syntax:

<button onClick={handleClick}>Click</button>

Vanilla JS Syntax:

document.getElementById("btn").onclick = handleClick;
  1. Event Listeners Are Attached to the Root (Event Delegation)

React attaches all event listeners to the root DOM instead of individual elements.

Why?
- Better Performance – Fewer event listeners, reducing memory usage.
- Works Well with Dynamic Elements – No need to rebind event handlers for newly added elements.

Vanilla JS (Event Attached to Each Button)

document.querySelectorAll("button").forEach(btn =>
  btn.addEventListener("click", () => console.log("Clicked"))
);

React (Single Listener at Root)

<button onClick={() => console.log("Clicked")}>Click Me</button>
  1. Prevents Default with event.preventDefault()

In vanilla JS, return false stops default behavior, but in React, you must use event.preventDefault() explicitly.

React Syntax:

function handleSubmit(event) {
  event.preventDefault();
  console.log("Form submitted");
}

<form onSubmit={handleSubmit}>
  <button type="submit">Submit</button>
</form>

Vanilla JS Syntax:

document.getElementById("form").onsubmit = function(event) {
  event.preventDefault();
};
  1. Events Are Automatically Pooled (Optimized for Performance)

React pools events, meaning they are reused for efficiency. If you need event details asynchronously, you must store them manually.

Wrong (Event Gets Nullified)

<button onClick={(e) => setTimeout(() => console.log(e.type), 1000)}>Click</button>

Correct (Store Event First)

<button onClick={(e) => {
  const eventType = e.type;
  setTimeout(() => console.log(eventType), 1000);
}}>Click</button>

Summary of Differences
- React uses Synthetic Events for cross-browser compatibility.
- Events use camelCase (onClick instead of onclick).
- React uses event delegation for better performance.
- Must use event.preventDefault() instead of return false.
- Events are pooled for optimization.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Describe the use case and implementation of suspense and lazy loading in React.

A

Suspense and Lazy Loading optimize performance by loading components only when needed, reducing the initial JavaScript bundle size.

Use Cases:
- Improve initial load time by deferring loading of non-essential components.
- Load heavy components (e.g., dashboards, modals) only when required.
- Implement route-based code splitting for better performance.

Key Benefits of Suspense & Lazy Loading
- Improves performance by reducing the initial JS bundle.
- Enhances user experience with fallback loading states.
- Optimizes resource usage by loading only necessary components.
- Great for large apps with complex routes and heavy UI components.

  1. Component-Level Lazy Loading with React.lazy()
  • Use React.lazy() to dynamically import components only when they are needed.

Example: Lazy Loading a Component

import React, { Suspense } from "react";

const LazyComponent = React.lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

How It Works?
- React.lazy() splits the component into a separate chunk.
- Suspense displays a fallback UI (Loading…) while the component loads asynchronously.

  1. Route-Based Lazy Loading with React Router
  • Dynamically load pages only when a user visits a route.

Example: Lazy Loading Routes

import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { lazy, Suspense } from "react";

const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));

function App() {
  return (
    <Router>
      <Suspense fallback={<p>Loading page...</p>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

Why Use This?
- Loads only the necessary JS chunks for the active route.
- Reduces initial page load time, improving performance.

  1. Suspense for Data Fetching (Experimental Feature)
  • Use Suspense with React 18 and data-fetching libraries like React Query or Relay.

Example: Suspense with Data Fetching (React 18 & Suspense-enabled APIs)

import { Suspense } from "react";

function DataComponent() {
  const data = fetchData(); // Assume fetchData is a React resource
  return <p>{data.name}</p>;
}

function App() {
  return (
    <Suspense fallback={<p>Loading data...</p>}>
      <DataComponent />
    </Suspense>
  );
}

Why Use This?
- Better user experience – Avoids blank screens while fetching data.
- Improved loading state handling with centralized fallback UIs.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

How can you use React.memo to optimize component rendering?

A

React.memo is a higher-order component (HOC) that optimizes performance by preventing unnecessary re-renders. It memoizes the output of a component and only re-renders it when its props change.

  1. Basic Usage of React.memo
  • Wrap a functional component with React.memo to avoid re-renders when props remain unchanged.
import React from "react";

const MemoizedComponent = React.memo(({ value }) => {
  console.log("Rendered!");
  return <p>Value: {value}</p>;
});

function App() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <MemoizedComponent value="Static Value" />
    </div>
  );
}

What Happens?
- MemoizedComponent won’t re-render when count changes because its value prop remains the same.
- This improves performance by avoiding unnecessary calculations inside the component.

  1. Using React.memo with Dynamic Props

By default, React.memo does a shallow comparison of props. If a prop is an object, array, or function, React may still trigger re-renders.

Example where memoization fails:

const MemoizedComponent = React.memo(({ obj }) => {
  console.log("Rendered!");
  return <p>{obj.value}</p>;
});

function App() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <MemoizedComponent obj={{ value: "Dynamic Object" }} />
    </div>
  );
}

Issue: Since { value: “Dynamic Object” } is a new object on every render, React.memo doesn’t prevent re-rendering.

  1. Custom Comparison Function

If you need deeper prop comparisons, you can pass a custom arePropsEqual function to React.memo.

Example: Custom Comparison for Objects

const MemoizedComponent = React.memo(
  ({ obj }) => {
    console.log("Rendered!");
    return <p>{obj.value}</p>;
  },
  (prevProps, nextProps) => prevProps.obj.value === nextProps.obj.value
);

Now the component will only re-render if obj.value actually changes.

  1. When NOT to Use React.memo
    - Don’t use it for small or frequently updated components (e.g., text inputs).
    - Avoid overusing it, as the memoization overhead may outweigh the performance benefits.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

What are the common pitfalls of using useEffect, and how can they be avoided?

A

useEffect is a powerful tool in React, but improper usage can lead to bugs, performance issues, and memory leaks. Here are common pitfalls and how to fix them:

  1. Missing Dependencies in the Dependency Array
    Problem: If dependencies are missing, the effect may not update correctly when state or props change.
  2. Infinite Loops Due to Incorrect Dependencies
    Problem: Including a changing state inside useEffect without proper handling can cause an infinite loop.
  3. Forgetting Cleanup for Subscriptions and Event Listeners
    Problem: Not cleaning up effects can lead to memory leaks, especially when using event listeners or API subscriptions.
    Example (Incorrect Usage)
useEffect(() => {
  window.addEventListener("resize", handleResize);
}, []); // Event listener stays active even after unmounting

Fix: Return a cleanup function inside useEffect to remove listeners on unmount.

  1. Fetching Data Without Handling Component Unmount
    Problem: Fetching data in useEffect without aborting can cause updates on an unmounted component.

Example: Use an AbortController to cancel the request if the component unmounts.

useEffect(() => {
  const controller = new AbortController();
  fetch("https://api.example.com/data", { signal: controller.signal })
    .then((res) => res.json())
    .then(setData)
    .catch((err) => {
      if (err.name !== "AbortError") console.error(err);
    });

  return () => controller.abort(); // Cleanup on unmount
}, []);
  1. Running Effects on Every Render Without Optimization
    Problem: A missing dependency array ([]) or passing unnecessary dependencies can cause effects to run on every render.

Example: If dependencies are dynamic, memoize them with useMemo or useCallback.

const memoizedValue = useMemo(() => computeExpensiveValue(data), [data]);
useEffect(() => {
  console.log(memoizedValue);
}, [memoizedValue]);
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
How do you handle errors in React components, and what are error boundaries?
Errors in React can occur due to issues in rendering, event handlers, network requests, or lifecycle methods. React provides different ways to handle them, including error boundaries. 1. Handling Errors in Event Handlers (`handleClick()`) Errors inside event handlers don’t crash the whole app because React automatically catches them. Use `try...catch` in Event Handlers 2. Handling Errors in API Calls (`try...catch`) For network requests, always handle errors gracefully to prevent UI crashes. 3. What Are Error Boundaries? Error boundaries catch JavaScript errors in child components and display a fallback UI instead of crashing the entire application. **Important Notes**: - Error boundaries only catch errors during **rendering**, **lifecycle methods**, and **constructors** of child components. - They don’t catch errors in event handlers, asynchronous code, or inside useEffect. ``` class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, info) { console.error("Error caught:", error, info); } render() { if (this.state.hasError) { return

Something went wrong.

; } return this.props.children; } } function BuggyComponent() { throw new Error("I crashed!"); return

This will never render

; } function App() { return ( ); } ``` 4. Handling Errors in Functional Components (`useErrorBoundary`) React does not provide hooks for error boundaries, but libraries like `react-error-boundary` simplify this. 5. When to Use Error Boundaries? - In UI-critical components (e.g., navigation, dashboards, modals). - When integrating third-party libraries that may throw errors. - For protecting individual sections of an app (e.g., wrapping only certain routes).
25
Explain the difference between optimistic and pessimistic updates in React.
Both optimistic and pessimistic updates are strategies for handling UI state when interacting with external data (like APIs). The main difference is when the UI updates relative to the API response. 1. Pessimistic Updates (Wait for Confirmation First) How It Works: - The UI waits for the API response before updating the state. - Ensures data consistency but feels slower. Example: Submitting a form and updating the UI only after the API confirms success. Pros: - Ensures data is correct before updating the UI. - Works well for critical operations (e.g., payments, database updates). Cons: - Feels slower, as the UI doesn’t update until the API responds. 2. Optimistic Updates (Update UI First, Then Confirm) How It Works: - The UI immediately updates as if the operation was successful. - If the API call fails, it rolls back the change. - Feels faster and more responsive. Example: A “Like” button that updates instantly while the API request runs in the background. Pros: - Instant feedback to the user. - Feels faster and more interactive. Cons: - Requires rollback logic if the API call fails. - May lead to temporary inconsistencies in data.
26
What is PropTypes, and how does it contribute to type checking in React?
`PropTypes` is a **runtime type-checking library** in React that helps ensure components receive props of the expected type and shape. It improves code reliability by catching potential bugs early. Why Use PropTypes? - Ensures correct prop types and prevents unexpected behavior. - Helps with debugging by warning about incorrect prop types in development. - Improves documentation by defining expected prop structures. **Conclusion**: PropTypes is useful for enforcing prop validation in React apps, especially in JavaScript projects where TypeScript isn’t used. Needs to be installed: `npm install prop-types` Limitations of PropTypes - Only works at runtime, unlike TypeScript, which provides compile-time safety. - Doesn’t prevent errors in production, only warns in development mode. - Requires manually defining types, whereas TypeScript offers automatic type inference. PropTypes vs. TypeScript: When to Use What? - Use PropTypes when working in JavaScript projects and need simple runtime validation. - Use TypeScript for static type checking, better tooling, and avoiding errors at compile time.
27
How can you implement dark mode in a React application?
You can implement it using React state, local storage, and CSS variables. 1. Using React State and Local Storage - persist across page reloads. Step 1: Set Up Theme Context (Optional but Recommended) Step 2: Apply Dark Mode Styles in CSS Step 3: Add a Toggle Button in the Component Step 4: Wrap the App with ThemeProvider Alternative: Using Tailwind CSS for Dark Mode If using Tailwind CSS, enable dark mode in tailwind.config.js: Then, toggle the class on document.documentElement:
28
Describe the role and benefits of using a CSS-in-JS library with React.
A CSS-in-JS library allows you to write styles directly in JavaScript, typically inside React components. Instead of using external .css or .scss files, styles are dynamically generated and scoped automatically. Popular CSS-in-JS Libraries - Styled-components - Emotion - Stitches - JSS Provide a component-based styling approach. Role of CSS-in-JS in React 1. Scoped Styles – Styles are component-specific, preventing conflicts. 2. Dynamic Styling – Styles can depend on component props or state. 3. Better Maintainability – Styles are co-located with components, making them easier to manage. 4. Performance Optimization – Some libraries generate styles only when needed and remove unused ones. Benefits of Using CSS-in-JS **1. Avoids Global Namespace Issues** Traditional CSS has a global scope, leading to class name conflicts. CSS-in-JS scopes styles to components automatically. **2. Supports Dynamic Styles** You can style components based on props or state. **3. Eliminates Unused CSS** CSS-in-JS generates styles only for the components that exist in the UI. No unused styles are shipped. **4. Encourages Component Reusability** Styled components can be exported and reused easily. **5. Works Well with Theming** Libraries like styled-components and Emotion support theme providers, making it easy to implement dark mode or custom themes. Drawbacks of CSS-in-JS - **Larger Bundle Size** – Generating styles dynamically adds runtime overhead. - **Slower SSR Performance** – CSS-in-JS libraries can slow down Server-Side Rendering (SSR) in frameworks like Next.js. - **Not Always Needed** – For simple projects, Tailwind CSS or plain CSS might be a better choice. When to Use CSS-in-JS? - If your app has dynamic styles (theme switching, prop-based styles). - If you want scoped styles without worrying about class name conflicts. - If you’re working on a component library with reusable styles. - If you’re building a large React project that requires maintainable styling.
29
What are the differences between useRef and createRef?
Both `useRef` and `createRef` are used to access DOM elements or persist values without causing re-renders, but they have key differences in behavior and use cases. `useRef` is preferred in modern React functional components due to its ability to persist values and avoid unnecessary re-renders. createRef is mainly for class components but is rarely needed in modern development. `useRef` (For Functional Components) - Persists the same reference across re-renders without resetting. - Does NOT cause re-renders when updated. - Can store DOM references or mutable values that survive re-renders. Example: ``` import { useRef, useEffect } from "react"; function FocusInput() { const inputRef = useRef(null); useEffect(() => { inputRef.current.focus(); // Focus input on mount }, []); return ; } ``` Why useRef? - The same inputRef persists across renders. - Changing inputRef.current does not trigger re-renders. `createRef` (For Class Components) - Creates a new reference on every render (does NOT persist across re-renders). - Triggers re-renders in class components. - Primarily used in class components to access child elements. Example: Using createRef in a Class Component ``` import React, { Component, createRef } from "react"; class FocusInput extends Component { inputRef = createRef(); componentDidMount() { this.inputRef.current.focus(); } render() { return ; } } ``` Why createRef? - Works well in class components but creates a new ref on each render, making it less efficient for repeated renders.
30
How can you handle data fetching in a React component?
The most common ways to handle it are `useEffect` with Fetch API/Axios, React Query, or SWR for better caching and performance. 1. Fetching Data with useEffect and fetch This is the standard way to fetch data in functional components. Why? - Uses useEffect to fetch data only once on mount. - Handles loading and error states. - Avoids memory leaks by ensuring the effect runs correctly. 2. Using Axios for Fetching Data Axios is a popular alternative to fetch with better error handling and automatic JSON parsing. (Also uses useEffect) Why Use Axios? - Simplifies API requests with built-in JSON parsing. - Supports request canceling (useful for avoiding state updates on unmounted components). - Provides better error handling with HTTP status codes. 3. Fetching Data with React Query (Recommended for Large Apps) React Query is a powerful library for handling data fetching with caching, automatic refetching, and background updates. Why Use React Query? - Automatic caching & background refetching. - Avoids unnecessary API calls when navigating between pages. - Built-in error handling & loading state management. 4. Using SWR for Data Fetching SWR (by Vercel) is another efficient data fetching and caching library. Why Use SWR? - Automatically updates stale data. - Less boilerplate than React Query. - Supports revalidation on focus (fetches fresh data when the user switches back to the tab). 5. Handling Fetch Cancellation (Avoiding State Updates After Unmount) To prevent memory leaks, use AbortController when fetching data manually. (inside the `useEffect`) Why? - Cancels API requests if the component unmounts. - Prevents setting state on an unmounted component. Example: ``` useEffect(() => { const controller = new AbortController(); const signal = controller.signal; fetch("https://jsonplaceholder.typicode.com/users", { signal }) .then((res) => res.json()) .then((data) => { setUsers(data); setLoading(false); }) .catch((err) => { if (err.name !== "AbortError") setError(err.message); }); return () => controller.abort(); // Cleanup on unmount }, []); ```
31
# React What are the best practices for structuring a React project?
**Best Practices for Structuring a React Project** 1. **Use a Scalable Folder Structure** – Organize files by features (`/components`, `/pages`, `/hooks`, `/services`). 2. **Keep Components Small & Reusable** – Avoid large components; break UI into reusable parts. 3. **Manage State Efficiently** – Use `useState` for local state, Context API/Zustand for global state, and Redux for complex apps. 4. **Move API Calls to Service Files** – Keep API logic separate (`/services/api.js`) for cleaner components. 5. **Use Custom Hooks for Shared Logic** – Extract reusable logic into `/hooks` (e.g., `useFetch`). 6. **Optimize Performance** – Use `React.memo`, `useCallback`, lazy loading, and virtualized lists for large datasets. 7. **Use Absolute Imports** – Set up a base path to avoid deep import chains. 8. **Organize Styles** – Use CSS modules or styled-components, and store global styles separately. 9. **Lazy Load Components** – Use `React.lazy()` and `Suspense` to load heavy components only when needed. 10. **Keep `package.json` Clean** – Regularly remove unused dependencies with `npm prune`.
32
# React How do you manage complex animations in React, and which libraries can be used?
To handle complex animations in React, you can use **CSS animations**, the **Web Animations API**, or specialized animation libraries. The best approach depends on performance, ease of use, and animation complexity. **Libraries for Animations in React** **Framer Motion** – Best for declarative and interactive animations. **React Spring** – Physics-based animations with fluid motion. **GSAP (GreenSock)** – Best for complex timelines and SVG animations. **Anime.js** – Lightweight JavaScript animation library for fine-grained control. Performance Optimization Tips - Use will-change in CSS to optimize rendering. - Prefer transform and opacity over layout-affecting properties (width, height). - Use requestAnimationFrame for smooth frame updates.
33
# React Write a `useState` hook that manages an array of items and provides functions to add and remove items.
``` const [items, setItems] = useState([]); const addItem = (item) => { setItems(prev => [...prev, item]); }; const removeItem = (id) => { setItems(prev => prev.filter(item => item.id !== id)); }; // Usage: addItem({id: 1, name: 'Item 1'}); ```
34
# React What happens if you update state with the same value in `useState`? Does the component re-render?
React uses `Object.is()` comparison to check if the state has changed. If the new value is the same as the current value, React **skips the re-render for performance optimization**. ``` const [count, setCount] = useState(0); setCount(0); // No re-render if count is already 0 setCount(prev => prev); // No re-render ```
35
# React How do you update nested object state correctly without mutating the original?
Always create a new object using spread syntax to avoid mutation: ``` const [user, setUser] = useState({name: 'John', profile: {age: 25}}); // Correct way - update nested property setUser(prev => ({ ...prev, profile: { ...prev.profile, age: 26 } })); // Wrong - mutates original object // user.profile.age = 26; setUser(user); ```
36
# React What's the difference between useState and useRef for storing values that change?
* `useState`: Triggers re-renders when updated, used for UI state * `useRef`: Doesn't trigger re-renders, persists across renders, used for mutable values ``` const [count, setCount] = useState(0); // Re-renders on change const countRef = useRef(0); // No re-render on change // countRef.current = 5; // Component won't re-render // setCount(5); // Component will re-render ```
37
# React Write a custom hook that manages form state with validation.
``` function useForm(initialValues, validate) { const [values, setValues] = useState(initialValues); const [errors, setErrors] = useState({}); const handleChange = (name, value) => { setValues(prev => ({...prev, [name]: value})); // Clear error when user starts typing if (errors[name]) { setErrors(prev => ({...prev, [name]: ''})); } }; const handleSubmit = (onSubmit) => (e) => { e.preventDefault(); const validationErrors = validate(values); setErrors(validationErrors); if (Object.keys(validationErrors).length === 0) { onSubmit(values); } }; return { values, errors, handleChange, handleSubmit }; } ```
38
# React How do you prevent infinite loops in `useEffect` when the dependency is an object or array?
Use `useMemo` or `useCallback` to memoize objects/arrays, or move them inside the effect: ``` // Problem - object recreated on every render const config = {url: '/api', method: 'GET'}; useEffect(() => { fetchData(config); }, [config]); // Infinite loop! // Solution 1 - memoize the object const config = useMemo(() => ({url: '/api', method: 'GET'}), []); // Solution 2 - move inside effect useEffect(() => { const config = {url: '/api', method: 'GET'}; fetchData(config); }, []); // Only dependency-free primitives ```
39
# React Write a `useEffect` that fetches data and handles cleanup to prevent memory leaks.
``` useEffect(() => { let isMounted = true; const controller = new AbortController(); const fetchData = async () => { try { const response = await fetch('/api/data', { signal: controller.signal }); const data = await response.json(); if (isMounted) { setData(data); } } catch (error) { if (error.name !== 'AbortError' && isMounted) { setError(error.message); } } }; fetchData(); return () => { isMounted = false; controller.abort(); }; }, []); ```
40
# React What's the difference between `useCallback` and `useMemo`? When would you use each?
* `useCallback`: Memoizes functions to prevent recreation * `useMemo`: Memoizes computed values to prevent recalculation ``` // useCallback - memoize function const handleClick = useCallback(() => { doSomething(id); }, [id]); // useMemo - memoize computed value const expensiveValue = useMemo(() => { return heavyCalculation(data); }, [data]); // Use useCallback for event handlers, useMemo for expensive computations ```
41
# React How do you share state between sibling components without using Context?
Lift state up to the nearest common parent component: ``` function Parent() { const [sharedData, setSharedData] = useState(''); return (
); } function ChildA({ data, setData }) { return ( setData(e.target.value)} /> ); } function ChildB({ data }) { return

Data from sibling: {data}

; } ```
42
# React Write a `useState` pattern for handling loading states during async operations.
``` function useAsyncState(initialData = null) { const [state, setState] = useState({ data: initialData, loading: false, error: null }); const setLoading = () => setState(prev => ({ ...prev, loading: true, error: null })); const setSuccess = (data) => setState({ data, loading: false, error: null }); const setError = (error) => setState(prev => ({ ...prev, loading: false, error })); return [state, { setLoading, setSuccess, setError }]; } // Usage: const [apiState, { setLoading, setSuccess, setError }] = useAsyncState(); ```
43
# React What is automatic batching in React 18, and how does it differ from React 17?
React 18 automatically batches multiple state updates into a single re-render, even in promises, timeouts, and event handlers. React 17 only batched updates in React event handlers. ``` // React 17 - 2 re-renders setTimeout(() => { setCount(c => c + 1); setFlag(f => !f); }, 1000); // React 18 - 1 re-render (automatically batched) // To opt out: use flushSync import { flushSync } from 'react-dom'; flushSync(() => setCount(c => c + 1)); setFlag(f => !f); // This will cause a separate re-render ```
44
# React When and how would you use `useTransition` and `startTransition`?
Use `useTransition` to mark state updates as non-urgent, allowing React to keep the UI responsive during expensive operations. ``` import { useTransition, startTransition } from 'react'; function SearchResults() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [isPending, startTransition] = useTransition(); const handleSearch = (value) => { setQuery(value); // Urgent - immediate update startTransition(() => { setResults(expensiveFilter(data, value)); // Non-urgent }); }; return (
handleSearch(e.target.value)} /> {isPending &&
Searching...
}
); } ```
45
# React How would you implement virtualization for a large list (10,000+ items)?
Use react-window or react-virtualized to render only visible items: ``` import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style, data }) => (
Item {data[index].name}
); function VirtualizedList({ items }) { return ( {Row} ); } // Only renders ~12 visible items instead of all 10,000 ```
46
# React How do you analyze and optimize bundle size in a React app?
Use webpack-bundle-analyzer and implement code splitting strategically: ``` // 1. Analyze bundle npm install --save-dev webpack-bundle-analyzer npm run build -- --analyze // 2. Split by routes const Home = lazy(() => import('./Home')); const Dashboard = lazy(() => import('./Dashboard')); // 3. Split large libraries const Chart = lazy(() => import('./Chart').then(module => ({ default: module.Chart })) ); // 4. Dynamic imports for features const handleExport = async () => { const { exportToPDF } = await import('./utils/export'); exportToPDF(data); }; ```
47
# React How do you test a component that fetches data with React Testing Library?
Mock the API call and test loading, success, and error states: ``` import { render, screen, waitFor } from '@testing-library/react'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import UserProfile from './UserProfile'; const server = setupServer( rest.get('/api/user', (req, res, ctx) => { return res(ctx.json({ name: 'John', email: 'john@test.com' })); }) ); beforeAll(() => server.listen()); afterAll(() => server.close()); test('displays user data after loading', async () => { render(); expect(screen.getByText('Loading...')).toBeInTheDocument(); await waitFor(() => { expect(screen.getByText('John')).toBeInTheDocument(); }); }); ```
48
# React How do you test a component that fetches data with React Testing Library?
Mock the API call and test loading, success, and error states: ``` import { render, screen, waitFor } from '@testing-library/react'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import UserProfile from './UserProfile'; const server = setupServer( rest.get('/api/user', (req, res, ctx) => { return res(ctx.json({ name: 'John', email: 'john@test.com' })); }) ); beforeAll(() => server.listen()); afterAll(() => server.close()); test('displays user data after loading', async () => { render(); expect(screen.getByText('Loading...')).toBeInTheDocument(); await waitFor(() => { expect(screen.getByText('John')).toBeInTheDocument(); }); }); ```
49
# React How do you test a custom hook that manages async state?
Use `@testing-library/react-hooks` or `renderHook` from React Testing Library: ``` import { renderHook, act } from '@testing-library/react'; import { useAsyncData } from './useAsyncData'; const mockFetch = jest.fn(); test('useAsyncData handles loading and success states', async () => { mockFetch.mockResolvedValueOnce({ data: 'test data' }); const { result } = renderHook(() => useAsyncData(mockFetch)); expect(result.current.loading).toBe(false); await act(async () => { await result.current.fetchData(); }); expect(result.current.loading).toBe(false); expect(result.current.data).toBe('test data'); expect(result.current.error).toBe(null); }); ```
50
# React How do you type a React component with optional props and event handlers?
``` interface ButtonProps { children: React.ReactNode; variant?: 'primary' | 'secondary'; disabled?: boolean; onClick?: (event: React.MouseEvent) => void; } const Button: React.FC = ({ children, variant = 'primary', disabled = false, onClick }) => { return ( ); }; // Usage with proper type checking
51
# React How do you create a generic component that works with different data types?
``` interface ListProps { items: T[]; renderItem: (item: T, index: number) => React.ReactNode; keyExtractor: (item: T) => string | number; } function List({ items, renderItem, keyExtractor }: ListProps) { return (
    {items.map((item, index) => (
  • {renderItem(item, index)}
  • ))}
); } // Usage - TypeScript infers T automatically {user.name}} keyExtractor={(user) => user.id} /> ```
52
# React When would you choose Context API vs Redux vs Zustand, and why?
* Context API: Simple global state (theme, auth), small-medium apps * Redux: Complex state logic, time-travel debugging, large teams * Zustand: Modern alternative to Redux, less boilerplate ``` // Zustand - simple and performant import { create } from 'zustand'; const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), })); // No providers needed, better performance than Context function Counter() { const { count, increment } = useStore(); return ; } ```
53
# React How do you implement optimistic updates with rollback capability?
``` function useOptimisticUpdate(apiCall, onSuccess) { const [data, setData] = useState([]); const [isLoading, setIsLoading] = useState(false); const optimisticUpdate = async (newItem, updateFn) => { // Store original state for rollback const previousState = data; // Apply optimistic update immediately setData(updateFn); setIsLoading(true); try { const result = await apiCall(newItem); // Confirm with server response setData(result); onSuccess?.(result); } catch (error) { // Rollback on failure setData(previousState); console.error('Update failed, rolling back:', error); } finally { setIsLoading(false); } }; return { data, isLoading, optimisticUpdate }; } ```
54
# React Convert this class component to a functional component with hooks. ``` class UserProfile extends React.Component { constructor(props) { super(props); this.state = { user: null, loading: true }; } componentDidMount() { this.fetchUserData(); } componentDidUpdate(prevProps) { if (prevProps.userId !== this.props.userId) { this.fetchUserData(); } } fetchUserData = () => { this.setState({ loading: true }); fetchUser(this.props.userId) .then(user => this.setState({ user })) .finally(() => this.setState({ loading: false })); }; render() { const { user, loading } = this.state; if (loading) return
Loading...
; return
{user?.name}
; } } ```
``` function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetchUser(userId).then(setUser).finally(() => setLoading(false)); }, [userId]); if (loading) return
Loading...
; return
{user?.name}
; } ```
55
# React How do you implement error boundaries in class components, and why can't you use hooks for this?
Error boundaries must be class components because they use special lifecycle methods that don't have hook equivalents: ``` class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null, errorInfo: null }; } static getDerivedStateFromError(error) { // Update state to trigger fallback UI return { hasError: true }; } componentDidCatch(error, errorInfo) { // Log error details this.setState({ error, errorInfo }); console.error('Error caught by boundary:', error, errorInfo); // Report to error tracking service // logErrorToService(error, errorInfo); } render() { if (this.state.hasError) { return (

Something went wrong

{this.props.fallback ||

Please refresh the page

}
); } return this.props.children; } } // Usage }> ```
56
# React What is the compound components pattern, and when would you use it?
Compound components work together as a cohesive unit, sharing implicit state through React Context. The parent component manages state while child components handle specific UI pieces. When to Use: * Building flexible, reusable UI components * When you need multiple components to work together (Modal, Tabs, Menu) * To avoid prop drilling in complex components * When you want to give users control over component composition Key Benefits: * Flexible API: Users compose components how they need * Implicit State Sharing: No need to pass props between related components * Separation of Concerns: Each sub-component has a single responsibility * Inversion of Control: Users control the structure, not the library ``` // Instead of this rigid API: // Compound components give flexibility: Delete User Are you sure you want to delete this user? ```
57
# React How would you structure a large React app with feature-based architecture?
Feature-based architecture organizes code by business features instead of file types. Each feature is self-contained with its own components, logic, and services. Why Feature-Based? * Easier to locate and modify related code * Better team collaboration (teams can own entire features) * Easier to delete or refactor entire features * Reduces coupling between unrelated parts src/ ├── shared/ # Code used across multiple features │ ├── components/ # Button, Modal, Input, etc. │ ├── hooks/ # useDebounce, useLocalStorage │ ├── utils/ # formatDate, validateEmail │ ├── types/ # Common TypeScript interfaces │ └── constants/ # API URLs, app config ├── features/ │ ├── auth/ # Everything authentication-related │ │ ├── components/ # LoginForm, SignupForm, etc. │ │ ├── hooks/ # useAuth, useLogin │ │ ├── services/ # authAPI, tokenService │ │ ├── types/ # User, LoginRequest types │ │ └── index.ts # Public API of this feature │ ├── dashboard/ # Dashboard feature │ └── user-profile/ # User profile management ├── app/ │ ├── store/ # Redux store or global state │ ├── router/ # Route definitions │ └── providers/ # App-wide context providers └── pages/ # Route components (Next.js) or App.tsx // Each feature exports only what others need: // features/auth/index.ts export { LoginForm, SignupForm } from './components'; export { useAuth } from './hooks/useAuth'; export type { User, AuthState } from './types'; Key Benefits: * Colocation: Related code lives together * Encapsulation: Private implementation details stay hidden * Scalability: Easy to add new features without affecting others
58
# React Implement the compound components pattern for a Modal system.
``` const ModalContext = createContext(); function Modal({ isOpen, onClose, children }) { return ( {isOpen && (
e.stopPropagation()}> {children}
)}
); } Modal.Header = function ModalHeader({ children }) { const { onClose } = useContext(ModalContext); return (
{children}
); }; Modal.Body = function ModalBody({ children }) { return
{children}
; }; Modal.Footer = function ModalFooter({ children }) { return
{children}
; }; // Usage - very flexible composition setShowModal(false)}> Confirm Action Are you sure you want to delete? ``` Why This Works: * Modal manages the overlay and close functionality * Each sub-component accesses shared state via context * Users can compose the modal however they need * Easy to add new modal parts (Modal.Actions, Modal.Icon, etc.)
59
# React What is the render props pattern, and how does it compare to custom hooks?
Render props is a pattern where a component receives a function as a prop that returns JSX. It allows sharing stateful logic between components. ``` class MouseTracker extends Component { state = { x: 0, y: 0 }; handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }); }; render() { return (
{this.props.render(this.state)}
); } } // Usage (

Mouse is at ({x}, {y})

)} /> ``` Modern Hook Equivalent: ``` function useMousePosition() { const [position, setPosition] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMouseMove = (e) => { setPosition({ x: e.clientX, y: e.clientY }); }; window.addEventListener('mousemove', handleMouseMove); return () => window.removeEventListener('mousemove', handleMouseMove); }, []); return position; } // Usage - cleaner and more composable function App() { const { x, y } = useMousePosition(); return

Mouse is at ({x}, {y})

; } ``` **When to use render props:** Legacy codebases, class components When to use hooks: Modern React, better composition, less nesting
60
# React Explain the Container/Presentational components pattern and its modern alternatives.
This pattern separates data fetching (Container) from UI rendering (Presentational). Containers handle logic, presentational components handle display. **Traditional Pattern:** ``` // Container Component (Smart) class UserContainer extends Component { state = { user: null, loading: true }; componentDidMount() { fetchUser(this.props.id).then(user => this.setState({ user, loading: false }) ); } render() { return ; } } // Presentational Component (Dumb) function UserPresentation({ user, loading }) { if (loading) return
Loading...
; return
{user.name}
; } ``` **Modern Hook-Based Approach:** ``` // Custom hook handles the "container" logic function useUser(id) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetchUser(id).then(user => { setUser(user); setLoading(false); }); }, [id]); return { user, loading }; } // Component focuses purely on presentation function UserProfile({ id }) { const { user, loading } = useUser(id); if (loading) return
Loading...
; return
{user.name}
; } ``` **Benefits of Modern Approach:** * Hooks are more reusable than container components * Easier to test logic separately from UI * Less component nesting * Better composition of multiple data sources
61
# React How would you implement the Provider pattern for theme management?
``` // Theme context and provider const ThemeContext = createContext(); const themes = { light: { background: '#ffffff', text: '#000000', primary: '#0066cc' }, dark: { background: '#1a1a1a', text: '#ffffff', primary: '#66aaff' } }; export function ThemeProvider({ children }) { const [currentTheme, setCurrentTheme] = useState('light'); const toggleTheme = () => { setCurrentTheme(prev => prev === 'light' ? 'dark' : 'light'); }; const themeValue = { theme: themes[currentTheme], currentTheme, toggleTheme }; return ( {children} ); } // Custom hook for consuming theme export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; } // Usage in components function Button({ children }) { const { theme } = useTheme(); return ( ); } // App setup function App() { return (
); } ``` **Why This Pattern Works:** * Centralized theme management * Type-safe with TypeScript * Easy to add new themes * Components automatically get theme updates
62
# React What is the Higher-Order Component (HOC) pattern, and when would you still use it over hooks?
HOCs are functions that take a component and return an enhanced version. While hooks are preferred, HOCs are still useful for certain scenarios. **HOC Example:** ``` function withAuth(WrappedComponent) { return function AuthenticatedComponent(props) { const { user, loading } = useAuth(); if (loading) return
Loading...
; if (!user) return ; return ; }; } // Usage const ProtectedProfile = withAuth(UserProfile); const ProtectedDashboard = withAuth(Dashboard); // Multiple HOCs can be composed const EnhancedComponent = withAuth(withLogging(withErrorBoundary(MyComponent))); ``` **When to Still Use HOCs:** * Cross-cutting concerns that affect multiple components * Legacy codebases with class components * Third-party library integration (React Router's withRouter) * Conditional rendering that wraps entire components **Modern Hook Alternative:** ``` function useAuthGuard() { const { user, loading } = useAuth(); const navigate = useNavigate(); useEffect(() => { if (!loading && !user) { navigate('/login'); } }, [user, loading, navigate]); return { user, loading, isAuthenticated: !!user }; } // Usage - more explicit function UserProfile() { const { user, loading } = useAuthGuard(); if (loading) return
Loading...
; return
{user.name}
; } ``` **HOCs vs Hooks:** **HOCs**: Good for wrapping/enhancing entire components **Hooks**: Better for sharing stateful logic, more composable
63
# React How would you implement a layered architecture in a large React application?
Layered architecture separates concerns into distinct layers, each with specific responsibilities. src/ ├── presentation/ # UI Layer │ ├── components/ # React components │ ├── pages/ # Route components │ ├── hooks/ # UI-specific hooks │ └── styles/ # Styling ├── application/ # Application Logic Layer │ ├── services/ # Business logic services │ ├── stores/ # State management │ ├── hooks/ # Business logic hooks │ └── types/ # Domain types ├── infrastructure/ # Infrastructure Layer │ ├── api/ # HTTP clients │ ├── storage/ # LocalStorage, sessionStorage │ ├── external/ # Third-party integrations │ └── config/ # Environment config └── domain/ # Domain Layer (Business Rules) ├── entities/ # Core business objects ├── repositories/ # Data access interfaces └── validators/ # Business rule validation Example Implementation: ``` // Domain Layer - Core business logic // domain/entities/User.ts export class User { constructor( public id: string, public email: string, public name: string ) {} isAdmin(): boolean { return this.email.endsWith('@company.com'); } } // Infrastructure Layer - External concerns // infrastructure/api/userApi.ts export class UserApiClient { async fetchUser(id: string): Promise { const response = await fetch(`/api/users/${id}`); return response.json(); } } // Application Layer - Orchestrates business logic // application/services/userService.ts export class UserService { constructor(private apiClient: UserApiClient) {} async getUser(id: string): Promise { const userData = await this.apiClient.fetchUser(id); return new User(userData.id, userData.email, userData.name); } } // Presentation Layer - React components // presentation/hooks/useUser.ts export function useUser(id: string) { const [user, setUser] = useState(null); const userService = useUserService(); // Injected dependency useEffect(() => { userService.getUser(id).then(setUser); }, [id]); return user; } ``` **Benefits:** **Testability**: Each layer can be tested independently **Maintainability**: Clear separation of concerns **Scalability**: Easy to modify one layer without affecting others **Team Collaboration**: Different teams can work on different layers
64
# React How do you mock API calls and test different response scenarios in React components?
Use MSW (Mock Service Worker) for realistic API mocking, or jest.mock for simpler cases. **MSW Setup:** ``` // Setup mock server const server = setupServer( rest.get('/api/users', (req, res, ctx) => { return res(ctx.json([{ id: 1, name: 'John' }])); }) ); // Test different scenarios test('handles loading and success states', async () => { render(); expect(screen.getByText('Loading...')).toBeInTheDocument(); await waitFor(() => { expect(screen.getByText('John')).toBeInTheDocument(); }); }); ``` **Benefits**: Realistic network behavior, easy to test error states, works across tests.
65
# React What's the difference between testing user interactions vs component internals?
**User interactions** test behavior (what users see/do), internals test implementation details. **Good - User Interaction:** ``` test('shows error when form is submitted empty', async () => { render(); await user.click(screen.getByRole('button', { name: 'Login' })); expect(screen.getByText('Email is required')).toBeVisible(); }); ``` **Avoid - Implementation Details:** ``` // Don't test state directly expect(component.state.errors).toEqual({ email: 'required' }); // Don't test if functions were called unless necessary expect(mockSetError).toHaveBeenCalledWith('required'); ``` **Why**: User-focused tests are more maintainable and catch real bugs users experience.
66
# React How do you test components that use React Router or other context providers?
Create test utilities with providers, or use library-specific testing helpers. **Custom Test Wrapper:** ``` function renderWithRouter(ui, { route = '/' } = {}) { window.history.pushState({}, 'Test page', route); return render(ui, { wrapper: BrowserRouter }); } // Usage test('navigates to profile page', async () => { renderWithRouter(, { route: '/profile' }); expect(screen.getByText('Profile Page')).toBeInTheDocument(); }); ``` **For Multiple Providers:** ``` const AllProviders = ({ children }) => ( {children} ); ``` **Tip**: Create reusable test utilities for common provider combinations.
67
# React How do you test accessibility and ensure your React components are accessible?
Use `@testing-library/jest-dom` matchers and `axe-core` for automated accessibility testing. **Basic Accessibility Tests:** ``` test('form is accessible', async () => { render(); // Check proper labels expect(screen.getByLabelText('Email')).toBeInTheDocument(); // Check keyboard navigation await user.tab(); expect(screen.getByRole('textbox', { name: 'Email' })).toHaveFocus(); // Check ARIA attributes expect(screen.getByRole('button')).toHaveAttribute('aria-disabled', 'false'); }); ``` **Automated Testing with axe:** ``` import { axe, toHaveNoViolations } from 'jest-axe'; expect.extend(toHaveNoViolations); test('should not have accessibility violations', async () => { const { container } = render(); const results = await axe(container); expect(results).toHaveNoViolations(); }); ``` **Why Test A11y**: Catches common issues like missing labels, poor color contrast, broken keyboard navigation.
68
# React How do you properly type event handlers and form elements in React with TypeScript?
Use specific React event types instead of generic Event types. **Event Handler Types:** ``` // Button click const handleClick = (e: React.MouseEvent) => { console.log(e.currentTarget.value); // Typed as HTMLButtonElement }; // Form submission const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); }; // Input change const handleChange = (e: React.ChangeEvent) => { setValue(e.target.value); // Typed correctly }; ``` **Generic Handler:** ``` type ButtonProps = { onClick?: (event: React.MouseEvent) => void; children: React.ReactNode; }; ``` **Benefits**: Autocomplete, type safety, prevents runtime errors.
69
# React How do you type React hooks and custom hooks with TypeScript?
Use generic types for flexibility and proper return type annotations. **Custom Hook Typing:** ``` // Generic hook for API calls function useApi(url: string): { data: T | null; loading: boolean; error: string | null; } { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // ... implementation return { data, loading, error }; } // Usage with type inference const { data, loading } = useApi('/api/users'); ``` **useState with Complex Types:** ``` interface FormState { email: string; password: string; errors: Record; } const [form, setForm] = useState({ email: '', password: '', errors: {} }); ``` **Key**: Let TypeScript infer when possible, be explicit when needed.
70
# React How do you create utility types for React components and handle optional/conditional props?
Use TypeScript utility types and conditional types for flexible component APIs. **Conditional Props:** ``` type ButtonProps = { children: React.ReactNode; variant: 'primary' | 'secondary'; } & ( | { href: string; onClick?: never } // Link button | { href?: never; onClick: () => void } // Regular button ); // This enforces either href OR onClick, not both ``` **Utility Types for Props:** ``` // Extract props from existing component type InputProps = React.ComponentProps<'input'>; // Make some props required type RequiredInputProps = InputProps & Required>; // Omit props you don't want type CustomButtonProps = Omit, 'onClick'> & { onPress: () => void; }; ``` **Generic Component Pattern:** ``` interface ListProps { items: T[]; renderItem: (item: T) => React.ReactNode; } function List({ items, renderItem }: ListProps) { return
    {items.map(renderItem)}
; } ```
71
# React What is `useDeferredValue` and when would you use it over `useTransition`?
`useDeferredValue` defers updates to a value, while `useTransition` defers state updates. Use `useDeferredValue` when you don't control the state update. **useDeferredValue Example:** ``` function SearchResults({ query }) { const deferredQuery = useDeferredValue(query); const results = useMemo(() => expensiveSearch(deferredQuery), [deferredQuery] ); return (
{query !== deferredQuery &&
Searching...
}
); } ``` **When to Use Each:** * **useDeferredValue**: When receiving props from parent, can't control the state update * **useTransition**: When you control the state update directly **Key Benefit**: Keeps UI responsive during expensive operations without blocking urgent updates.
72
# React How does Suspense work with data fetching in React 18, and what are Suspense boundaries?
React 18 Suspense can handle data fetching (with compatible libraries), creating loading boundaries throughout your app. **Suspense Boundaries:** ``` function App() { return ( }>
}> }> ); } ``` **With Data Fetching (React Query + Suspense):** ``` // Enable suspense mode const queryClient = new QueryClient({ defaultOptions: { queries: { suspense: true } } }); function UserProfile({ id }) { const { data: user } = useQuery(['user', id], fetchUser); return
{user.name}
; // No loading state needed } ``` **Benefits**: Declarative loading states, better UX with nested loading, automatic error boundaries integration.
73
# React What are the main security considerations when building React applications?
Focus on XSS prevention, secure authentication, and safe data handling. **XSS Prevention:** ``` // Safe - React escapes by default
{userInput}
// Dangerous - bypasses escaping
// Safe HTML rendering import DOMPurify from 'dompurify';
``` **Authentication Security:** * Store JWT in httpOnly cookies, not localStorage * Implement proper CSRF protection * Use secure headers (CSP, HSTS) * Validate permissions on both client and server **Environment Variables:** ``` // Safe - server-side only process.env.SECRET_KEY // Dangerous - exposed to client process.env.REACT_APP_SECRET_KEY ``` **Key Rule**: Never trust client-side validation or security—always validate on server.
74
# React How do you optimize React apps for production deployment and monitoring?
Focus on bundle optimization, performance monitoring, and error tracking. **Build Optimization:** ``` // webpack-bundle-analyzer npm run build -- --analyze // Key optimizations: // 1. Code splitting by route const Home = lazy(() => import('./Home')); // 2. Tree shaking - import only what you need import { debounce } from 'lodash/debounce'; // Not entire lodash // 3. Optimize images import { lazy } from 'react'; const Image = lazy(() => import('./OptimizedImage')); ``` **Production Monitoring:** ``` // Error boundaries with logging componentDidCatch(error, info) { // Send to monitoring service Sentry.captureException(error, { contexts: { react: info } }); } // Performance monitoring import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'; getCLS(console.log); getFID(console.log); // Track Core Web Vitals ``` **Deployment Checklist:** * Enable gzip/brotli compression * Set proper cache headers * Use CDN for static assets * Implement health checks * Set up alerts for errors/performance