All topics
React Interview Questions & Answers
46 questions with detailed answers — for freshers and experienced candidates.
Want to actually learn React?
Join a hands-on mini internship or training on iCampusLink and earn a certificate.
Explore programs →Fresher Level
Q1. What is React and what are its key features?
React is an open-source JavaScript library for building user interfaces, particularly single-page applications where data changes over time. Developed by Facebook, it allows developers to create reusable UI components.
Key features include:
1. **Declarative UI**: React makes it easier to create interactive UIs by letting you describe how the UI should look for a given state, and React efficiently updates and renders the right components when your data changes.
2. **Component-Based**: Applications are built from encapsulated components that manage their own state, making UI development modular and reusable.
3. **Virtual DOM**: React uses a virtual DOM, which is a lightweight copy of the actual DOM. It improves performance by minimizing direct DOM manipulations, only updating the necessary parts.
4. **JSX**: A syntax extension for JavaScript, allowing you to write HTML-like code within JavaScript, making component structure intuitive.
5. **One-way Data Flow**: Data flows in a single direction (from parent to child components), making it easier to understand and debug.
Q2. Explain the concept of JSX in React.
JSX (JavaScript XML) is a syntax extension for JavaScript, recommended by React. It allows you to write HTML-like code directly within your JavaScript files, which is then transpiled into regular JavaScript calls (specifically, `React.createElement()`) by tools like Babel.
JSX makes writing React components more intuitive and readable by visually representing the UI structure alongside the component's logic. It's not mandatory to use JSX with React, but it's widely adopted due to its benefits in readability and expressiveness. It enables embedding JavaScript expressions inside curly braces `{}` and prevents injection attacks by escaping values before rendering.
const name = 'React Developer';
const element = <h1>Hello, {name}!</h1>;
// This JSX is transpiled to:
// const element = React.createElement('h1', null, 'Hello, ', name, '!');
Q3. What are Props in React?
Props (short for properties) are a mechanism for passing data from a parent component to a child component in React. They are read-only, meaning a child component cannot directly modify the props it receives from its parent. This enforces a unidirectional data flow, making applications easier to understand and debug.
Props are passed as attributes to a component, similar to HTML attributes. Inside the child component, they are accessed as an object. They are crucial for making components reusable and configurable, allowing them to display different data or behave differently based on the input they receive.
// Parent Component
function Welcome() {
return <Greeting name="Alice" />;
}
// Child Component
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
Q4. Explain the concept of State in React functional components.
State in React represents the data that a component manages and can change over time, typically in response to user actions or network requests. When the state of a component changes, React re-renders that component and its children to reflect the updated data.
In functional components, state is managed using the `useState` hook. `useState` returns an array containing the current state value and a function to update it. It takes an initial state value as an argument. The updater function should be used to change the state, as direct modification of the state variable will not trigger a re-render.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Q5. How do you handle events in React?
Event handling in React is similar to handling events in plain HTML, but with some syntactical differences. React events are named using camelCase (e.g., `onClick` instead of `onclick`) and you pass a function as the event handler, rather than a string.
React normalizes events across different browsers, meaning the event object passed to your handler will have consistent properties. You can define event handler functions directly within the component or as separate functions. For class components, you often need to bind `this` to the event handler, but in functional components with hooks, this is generally not an issue as arrow functions inherently bind `this` to the surrounding context or you don't use `this` at all.
function ButtonClicker() {
const handleClick = (event) => {
alert('Button clicked!');
console.log(event.target); // The DOM element that triggered the event
};
return (
<button onClick={handleClick}>
Click Me
</button>
);
}
Q6. What is conditional rendering in React?
Conditional rendering in React allows you to render different elements or components based on certain conditions. It's a fundamental concept for building dynamic user interfaces, where parts of the UI might appear or disappear depending on the application's state or props.
Common techniques for conditional rendering include:
1. **`if`/`else` statements**: Used outside JSX or within a function that returns JSX.
2. **Ternary operator (`condition ? true : false`)**: Concise for inline conditions.
3. **Logical `&&` operator**: For rendering something only when a condition is true (if the condition is false, React ignores and skips it).
4. **Switch statements**: For multiple conditions.
function UserGreeting(props) {
const isLoggedIn = props.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<h1>Welcome back!</h1>
) : (
<h1>Please sign up.</h1>
)}
</div>
);
}
Q7. What is the Virtual DOM in React?
The Virtual DOM (VDOM) is a programming concept where a virtual representation of the UI is kept in memory and synced with the 'real' DOM by a library like React DOM. It's a lightweight copy of the actual DOM.
When state or props in a React component change, React first creates a new Virtual DOM tree. It then compares this new tree with the previous Virtual DOM tree using an algorithm called 'diffing'. This process identifies the minimal set of changes required to update the actual DOM. Finally, React updates only those specific parts of the real DOM that have changed, rather than re-rendering the entire page. This selective updating is much faster than directly manipulating the browser's DOM, significantly improving performance and user experience.
Q8. What are React Fragments and why are they useful?
React Fragments allow you to group multiple elements without adding an extra node to the DOM. In React, components traditionally need to return a single parent element. If you needed to return multiple sibling elements, you'd typically wrap them in a `div`.
However, adding unnecessary `div`s can sometimes lead to issues with styling (e.g., Flexbox, CSS Grid) or semantic HTML. Fragments solve this problem by providing a way to group elements without introducing an additional DOM node. They can be written explicitly as `<React.Fragment></React.Fragment>` or using the shorthand `<></>`.
import React from 'react';
function MyComponent() {
return (
<>
<h1>Title</h1>
<p>This is a paragraph.</p>
</>
);
}
// Without Fragment, you'd need a div:
// <div>
// <h1>Title</h1>
// <p>This is a paragraph.</p>
// </div>
Q9. How do you create a new React application?
The most common and recommended way to create a new single-page React application is by using `Create React App` (CRA). CRA is an officially supported toolchain that sets up a new React project with a sensible default configuration, including build tools like Webpack and Babel, without requiring any manual configuration.
To create a new app, you open your terminal or command prompt and run one of the following commands:
Using `npx` (recommended, as it uses the latest version of CRA without installing it globally):
npx create-react-app my-app
Using `yarn`:
yarn create react-app my-app
After the command completes, navigate into your new project directory (`cd my-app`) and start the development server using `npm start` or `yarn start`. This will open your application in the browser at `http://localhost:3000` (by default).Q10. Explain the concept of component reusability in React.
Component reusability in React refers to the ability to design and build components that can be used in multiple parts of an application, or even across different applications, without significant modifications. This is a core principle of React's component-based architecture and leads to more efficient, maintainable, and scalable codebases.
Key aspects that enable reusability:
1. **Props**: Components receive data and configuration via props, allowing them to display different content or behave differently based on the input they receive.
2. **State Management**: Components manage their own internal state, encapsulating their behavior.
3. **Composition**: Complex UIs are built by composing smaller, simpler components together.
4. **Separation of Concerns**: Components should ideally do one thing well, making them easier to understand and reuse.
5. **Generic vs. Specific**: Designing components to be generic (e.g., a `Button` component) rather than highly specific (e.g., an `AddToCartButton`) increases their reusability.
// Reusable Button component
function Button({ onClick, children, type = 'button' }) {
return (
<button type={type} onClick={onClick}>
{children}
</button>
);
}
// Usage:
// <Button onClick={handleSave}>Save</Button>
// <Button type="submit">Submit Form</Button>
Q11. What is the significance of the `key` prop when rendering lists in React?
The `key` prop is a special string attribute that you need to include when creating lists of elements in React. Keys help React identify which items in a list have changed, are added, or are removed. They give a stable identity to each element in the list, allowing React to efficiently update the UI during its reconciliation process.
Without unique keys, React might struggle to correctly identify and re-render only the changed items. This can lead to several issues:
1. **Performance Problems**: React may re-render the entire list or larger portions than necessary.
2. **Incorrect Component State**: If list items change order, React might reuse component instances with the wrong state.
3. **Unexpected Behavior**: Input fields might lose focus, or checkboxes might not reflect their correct checked state.
Keys should be unique among sibling elements in the list. Using an item's unique ID from the data is the best practice. Using array indices as keys is generally discouraged if the list items can be reordered, added, or removed, as it can lead to incorrect behavior.
const products = [
{ id: 1, name: 'Laptop' },
{ id: 2, name: 'Mouse' },
];
function ProductList() {
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
Q12. What is the purpose of `StrictMode` in React?
React's `StrictMode` is a development tool that helps identify potential problems in an application. It doesn't render any visible UI itself but activates additional checks and warnings for its descendants. It's similar to `console.log` for components.
`StrictMode` runs checks and warnings in development mode only; it has no impact on the production build. Its primary purposes are:
1. **Identifying unsafe lifecycle methods**: For class components, it warns about deprecated lifecycle methods that can lead to bugs.
2. **Warning about legacy string ref API usage**.
3. **Detecting unexpected side effects**: It intentionally double-invokes certain functions (like `render` and `useEffect` callbacks) to help you find side effects that might occur during an interrupted render, which is crucial for future concurrent features.
4. **Warning about deprecated API usage**: Such as `findDOMNode`.
5. **Detecting legacy Context API usage**.
By wrapping a part of your application with `<React.StrictMode>`, you can get immediate feedback on potential issues, helping you write more robust and future-proof React code.
import React from 'react';
function App() {
return (
<React.StrictMode>
<MyComponent />
</React.StrictMode>
);
}
Q13. What is the difference between a component and an element in React?
In React, the terms 'component' and 'element' are often used interchangeably, but they represent distinct concepts:
1. **Component**: A component is a blueprint or a template. It's a JavaScript function or a class that defines how a piece of UI looks and behaves. It takes props as input and returns React elements. Components are reusable, self-contained units of UI logic.
// Functional Component
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
2. **Element**: An element is a plain JavaScript object that describes what you want to see on the screen. It's a lightweight description of a DOM node or a component instance. React elements are the immutable output of components' `render` methods. They are not actual DOM nodes, but rather instructions for React DOM on how to construct the DOM.
// React Element (returned by a component or written directly as JSX)
const element = <Welcome name="Alice" />;
// This is equivalent to:
// const element = React.createElement(Welcome, { name: "Alice" });
In essence, a component is a *function* or *class*, while an element is a *plain object* that describes an instance of a component or a DOM node.Q14. What is `ReactDOM.render()` and `ReactDOM.createRoot()`?
`ReactDOM.render()` was the traditional method for rendering a React component into a DOM node. It mounted the React element into the provided `container` DOM element and returned a reference to the component instance. However, it was designed for synchronous rendering and didn't fully support React's new concurrent features.
`ReactDOM.createRoot()` is the new root API introduced in React 18. It's the recommended way to bootstrap a React application and is necessary to opt into React's new concurrent renderer. `createRoot()` creates a React root, which is an entry point for React to manage the DOM inside a browser element. After creating a root, you call its `render` method to render a React element.
**Key differences**:
* `createRoot()` enables concurrent features (like automatic batching, transitions, and Suspense for data fetching).
* `render()` is synchronous; `createRoot().render()` is designed for asynchronous and interruptible rendering.
* `render()` will eventually be deprecated; `createRoot()` is the future.
// Old way (React 17 and below)
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));
// New way (React 18+)
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Intermediate Level
Q1. What is the `useEffect` hook and when would you use it?
The `useEffect` hook in React functional components allows you to perform 'side effects' after rendering. Side effects are operations that interact with the outside world, such as data fetching, subscriptions, manually changing the DOM, timers, or logging. It's a replacement for lifecycle methods like `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` in class components.
`useEffect` takes two arguments: a function containing the effect logic, and an optional dependency array. If the dependency array is provided, the effect will only re-run if any value in the array changes between renders. An empty dependency array (`[]`) means the effect runs only once after the initial render (like `componentDidMount`). Omitting the dependency array means the effect runs after every render.
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // Re-run effect only when count changes
return (
<button onClick={() => setCount(count + 1)}>
Increment ({count})
</button>
);
}
Q2. Explain the `useContext` hook and the Context API.
The React Context API provides a way to pass data through the component tree without having to pass props down manually at every level (prop drilling). It's designed to share data that can be considered 'global' for a tree of React components, such as the current authenticated user, theme, or preferred language.
The `useContext` hook is used in functional components to subscribe to context changes. It takes a Context object (created with `React.createContext()`) as an argument and returns the current context value for that context. When the Context Provider's value changes, `useContext` will trigger a re-render of the components consuming it.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function MyComponent() {
const theme = useContext(ThemeContext);
return <p>Current theme: {theme}</p>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<MyComponent />
</ThemeContext.Provider>
);
}
Q3. What is the `useRef` hook and when would you use it?
The `useRef` hook returns a mutable ref object whose `.current` property is initialized to the passed argument (`initialValue`). The returned ref object will persist for the full lifetime of the component. It's primarily used for three purposes:
1. **Accessing DOM elements directly**: The most common use case is to get a direct reference to a DOM node or a React component instance.
2. **Storing mutable values that don't trigger re-renders**: Unlike `useState`, updating a ref's `.current` value does not trigger a re-render. This is useful for storing values that need to persist across renders but don't need to be part of the reactive data flow (e.g., timer IDs, previous values).
3. **Referencing instance variables**: Similar to instance variables in class components.
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Q4. Explain `useCallback` and `useMemo` hooks. When should you use them?
`useCallback` and `useMemo` are optimization hooks that help prevent unnecessary re-renders of components by memoizing values and functions.
* **`useCallback`**: Memoizes a function. It returns a memoized version of the callback function that only changes if one of the `dependencies` has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g., `React.memo`).
* **`useMemo`**: Memoizes a value. It computes a value and caches the result. It only re-computes the memoized value when one of the `dependencies` has changed. This is useful for expensive calculations that don't need to be re-run on every render.
Both should be used judiciously, as memoization itself has a cost. They are most beneficial in performance-critical scenarios, such as when dealing with large lists, complex calculations, or when passing props to `React.memo` wrapped components.
import React, { useState, useCallback, useMemo } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const expensiveValue = useMemo(() => {
// Some heavy calculation based on count
return count * 2;
}, [count]);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // Empty dependency array means this function reference never changes
return (
<div>
<p>Count: {count}</p>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
Q5. What is the difference between `useState` and `useReducer`?
Both `useState` and `useReducer` are hooks for managing state in functional components, but they are suited for different scenarios.
* **`useState`**: Ideal for simple state management where the state logic is straightforward (e.g., toggling a boolean, incrementing a number). It returns the current state and a setter function.
const [count, setCount] = useState(0);
* **`useReducer`**: Better suited for complex state logic involving multiple sub-values or when the next state depends on the previous one. It's often preferred when state transitions are complex, involve multiple actions, or when you need to manage global state with a reducer pattern similar to Redux. It returns the current state and a `dispatch` function, which you call with an action object.
const [state, dispatch] = useReducer(reducer, initialState);
`useReducer` offers more predictable state updates and can be easier to test and debug for complex scenarios, especially when state logic is extracted into a separate reducer function.Q6. Describe React's Reconciliation process.
Reconciliation is the algorithm React uses to update the UI efficiently. When a component's state or props change, React creates a new virtual DOM tree. It then compares this new tree with the previous virtual DOM tree to determine the minimal set of changes needed to update the actual browser DOM.
This comparison (often called 'diffing') follows specific heuristics:
1. **Different element types**: If the root elements have different types (e.g., `<div>` to `<span>`), React tears down the old tree and builds the new one from scratch.
2. **Same element type**: If the element types are the same, React looks at the attributes (props) and only updates those that have changed.
3. **Children**: When recursing on children, React iterates over both lists of children simultaneously. If a child element has a `key` prop, React uses it to match children from the old list to the new list, allowing it to efficiently move, add, or remove elements without recreating them.
This process ensures that direct DOM manipulation, which is expensive, is minimized, leading to better performance.
Q7. What are Higher-Order Components (HOCs) in React?
A Higher-Order Component (HOC) is an advanced technique in React for reusing component logic. HOCs are not components themselves; rather, they are functions that take a component as an argument and return a new component with enhanced functionality.
HOCs are commonly used for tasks like:
* **Code reuse**: Abstracting common logic that can be shared across multiple components.
* **Prop manipulation**: Adding, removing, or transforming props.
* **State abstraction**: Managing state that is common to several components.
* **Conditional rendering**: Showing or hiding components based on certain conditions.
For example, a HOC might add a loading spinner while data is being fetched, or inject authentication data into a component. While still valid, with the advent of hooks, HOCs are less frequently used for stateful logic, but remain useful for cross-cutting concerns like logging or authorization.
function withLogger(WrappedComponent) {
return function WithLogger(props) {
console.log(`Component ${WrappedComponent.name} rendered.`);
return <WrappedComponent {...props} />;
};
}
const MyComponentWithLogger = withLogger(MyComponent);
Q8. Explain the concept of Render Props.
The term "render prop" refers to a technique for sharing code between React components using a prop whose value is a function. This function returns a React element and allows the component to delegate its rendering logic to the consumer of the component.
Instead of passing a component directly, you pass a function as a prop. This function receives data or state from the component and returns JSX. This pattern enables flexible and reusable components without the overhead of HOCs or custom hooks for simple cases. It's particularly useful for cross-cutting concerns like data fetching, tracking mouse position, or managing state that multiple components might need.
import React, { useState } from 'react';
function Toggle(props) {
const [on, setOn] = useState(false);
const toggler = () => setOn(!on);
return props.render({ on, toggler }); // The render prop is called here
}
function App() {
return (
<Toggle render={({ on, toggler }) => (
<div>
{on ? 'ON' : 'OFF'}
<button onClick={toggler}>Toggle</button>
</div>
)} />
);
}
Q9. How do you optimize performance in React applications?
Optimizing React performance involves minimizing unnecessary renders and expensive computations. Key strategies include:
1. **Memoization**: Using `React.memo` for components and `useMemo`/`useCallback` hooks for values and functions to prevent re-computation or re-rendering if props/dependencies haven't changed.
2. **Lazy Loading/Code Splitting**: Using `React.lazy` and `Suspense` to load components only when they are needed, reducing the initial bundle size.
3. **Virtualization/Windowing**: For long lists, only rendering the visible items in the DOM to improve performance.
4. **Optimizing Context**: Avoiding placing rapidly changing state directly in Context if it's consumed by many components, or splitting context into smaller, more granular contexts.
5. **Keys for Lists**: Using unique and stable `key` props when rendering lists to help React efficiently reconcile updates.
6. **Debouncing/Throttling**: Limiting the rate at which a function is called, especially for event handlers like `onScroll` or `onInputChange`.
7. **Profiling**: Using React DevTools Profiler to identify performance bottlenecks.
8. **Immutable Data Structures**: Making state updates immutable can help React's shallow comparison in `React.memo` and `shouldComponentUpdate` work more effectively.
Q10. What are React Portals and when would you use them?
React Portals provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. Normally, a component's render method returns elements that are mounted as children of the closest parent DOM node.
With a Portal, you can render a child component into a different DOM element, specified by `ReactDOM.createPortal(child, container)`. This is particularly useful for scenarios where a component needs to break out of its parent's CSS `overflow` or `z-index` properties, such as:
* **Modals, dialogs, and popups**: These often need to be positioned at the top level of the DOM to ensure they appear above all other content.
* **Tooltips or dropdowns**: When they need to escape their parent's boundaries.
Despite being rendered elsewhere in the DOM, a portal's children still behave like normal React children within the component tree, meaning they can access context and participate in event bubbling.
Q11. How do you handle forms in React? Differentiate between controlled and uncontrolled components.
Handling forms in React primarily involves managing the state of input elements. There are two main approaches:
1. **Controlled Components**: The form elements (inputs, textareas, selects) are controlled by React state. The input's value is stored in the component's state, and any changes to the input trigger an `onChange` event handler, which updates the state. This makes the input value always reflect the state, giving React full control. This is the recommended approach for most cases as it simplifies validation and manipulation.
function ControlledForm() {
const [value, setValue] = useState('');
return (
<input type="text" value={value} onChange={e => setValue(e.target.value)} />
);
}
2. **Uncontrolled Components**: Form elements maintain their own internal state, similar to traditional HTML forms. React doesn't manage their state. You typically use a `ref` to get the current value directly from the DOM when you need it (e.g., on form submission). This can be simpler for very basic forms but offers less control and can make validation more cumbersome.
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = () => alert(inputRef.current.value);
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
}
Q12. What are custom hooks in React and why are they useful?
Custom hooks are JavaScript functions whose names start with `use` and that can call other hooks (like `useState`, `useEffect`, `useContext`, etc.). They are a powerful mechanism in React for extracting reusable stateful logic from components.
Instead of repeating the same logic across multiple components (e.g., fetching data, handling form input, managing timers), you can encapsulate that logic within a custom hook. This allows you to share stateful behavior without sharing UI, promoting code reuse, better organization, and improved readability.
Custom hooks don't create new components; they simply provide a way to reuse logic. When you use a custom hook, all the state and effects inside it are isolated to that component, ensuring independent state management for each component that uses the hook.
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
// Usage in a component:
// function MyComponent() {
// const width = useWindowWidth();
// return <p>Window width: {width}px</p>;
// }
Q13. How do you pass data between sibling components in React?
Data transfer between sibling components typically follows a pattern known as 'lifting state up' or using a shared state management solution.
1. **Lifting State Up**: The most common and React-idiomatic way. If two sibling components need to share or communicate data, their common parent component should hold the shared state. The parent passes the data down to each sibling as props. If a sibling needs to update the shared data, the parent passes a callback function as a prop to that sibling. The sibling calls this callback, which updates the parent's state, and the updated state is then passed down to both siblings, triggering re-renders.
2. **Context API**: For data that needs to be accessed by many components at different nesting levels, the Context API can be used to avoid prop drilling.
3. **State Management Libraries**: For complex applications with global state, libraries like Redux, Zustand, or Jotai provide centralized stores that siblings (and any other components) can subscribe to.
// Parent Component
function Parent() {
const [sharedData, setSharedData] = useState('');
return (
<div>
<SiblingA data={sharedData} />
<SiblingB onDataChange={setSharedData} />
</div>
);
}
// Sibling A receives data as prop
function SiblingA({ data }) { return <p>Data from SiblingB: {data}</p>; }
// Sibling B sends data up via callback
function SiblingB({ onDataChange }) {
return <button onClick={() => onDataChange('Hello from B')}>Send Data</button>;
}
Q14. What is 'prop drilling' and how can you avoid it?
Prop drilling (also known as 'thread props' or 'prop passing') is a scenario where data is passed down through multiple nested child components, even if those intermediate components don't directly need the data. They simply act as conduits, forwarding the data further down the component tree.
Prop drilling can make components less reusable, harder to refactor, and can increase the complexity of understanding data flow, especially in large applications.
Ways to avoid prop drilling:
1. **Context API**: For data that is truly global or needed by many components at different levels, Context provides a way to make data available to any component within its provider's subtree without explicit prop passing.
2. **Component Composition**: Restructuring components to pass only what's necessary, or making components accept children, allowing the parent to render components directly that need specific data.
3. **State Management Libraries**: Libraries like Redux, Zustand, or Jotai offer centralized stores where components can directly subscribe to the data they need, bypassing intermediate components.
4. **Custom Hooks**: For reusable stateful logic, custom hooks can encapsulate and provide data to components that use them, reducing the need for props.
Q15. Explain the concept of immutability in React state.
Immutability in React state means that you should never directly modify the existing state object or array. Instead, when you need to update state, you should create a new object or array with the desired changes and then use the state setter function (`setState` in class components or the setter returned by `useState` in functional components) to replace the old state with the new one.
This is crucial for several reasons:
1. **Detecting Changes**: React's reconciliation algorithm relies on shallow comparison to detect if a component needs to re-render. If you mutate state directly, the reference to the object/array remains the same, and React might not detect the change, leading to UI inconsistencies.
2. **Predictability**: Immutable updates make state changes more predictable and easier to track, which is beneficial for debugging, testing, and implementing features like undo/redo.
3. **Performance**: When used with `React.memo` or `shouldComponentUpdate`, immutable updates allow React to quickly determine if a component's props or state have truly changed, preventing unnecessary re-renders.
// Incorrect (mutates state directly)
// const [items, setItems] = useState(['A', 'B']);
// items.push('C');
// setItems(items);
// Correct (creates new array)
const [items, setItems] = useState(['A', 'B']);
setItems(prevItems => [...prevItems, 'C']);
Q16. What are default props in React?
Default props are a way to assign default values to a component's props if no value is explicitly passed by the parent component. This ensures that a component always has a value for certain props, preventing `undefined` errors and making the component more robust and easier to use.
They are particularly useful for optional props, where you want to provide a sensible fallback if the consumer of your component doesn't provide a value. Default props can be defined directly on the functional component using the `defaultProps` static property or by using default values in destructuring for functional components.
// Functional Component with default props via destructuring
function Button({ label = 'Click Me', color = 'blue' }) {
return <button style={{ backgroundColor: color }}>{label}</button>;
}
// Functional Component with static defaultProps (older syntax, still valid)
// Button.defaultProps = {
// label: 'Click Me',
// color: 'blue'
// };
// Usage:
// <Button /> // Renders a blue button with 'Click Me'
// <Button label="Submit" /> // Renders a blue button with 'Submit'
Q17. How do you fetch data in React components?
Data fetching in React components is typically handled using the `useEffect` hook for functional components. The `useEffect` hook allows you to perform side effects, such as network requests, after the component renders.
Inside `useEffect`, you define an asynchronous function to fetch data (e.g., using `fetch` or Axios). It's crucial to handle the loading state, error state, and the data itself. The dependency array of `useEffect` is vital: an empty array (`[]`) ensures the fetch happens only once after the initial render (like `componentDidMount`). If the fetch depends on props or state, include them in the dependency array.
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) throw new Error('Network response was not ok.');
const data = await response.json();
setUser(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // Re-fetch if userId changes
if (loading) return <div>Loading user...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>No user found.</div>;
return <div><h1>{user.name}</h1><p>{user.email}</p></div>;
}
Q18. What is the difference between client-side rendering (CSR) and server-side rendering (SSR)?
The primary difference lies in where the initial HTML for a web page is generated and how the application becomes interactive.
**Client-Side Rendering (CSR)**:
* **Process**: The browser receives a minimal HTML file (often just a `div` and a script tag). JavaScript then fetches data, renders the UI, and makes the application interactive.
* **Pros**: Fast transitions after initial load, less server load, good for highly interactive applications.
* **Cons**: Slower initial load (blank screen until JS loads), poor SEO (crawlers might struggle with empty HTML), requires JS to be enabled.
**Server-Side Rendering (SSR)**:
* **Process**: The server renders the initial HTML for the page, which includes the content. This HTML is sent to the browser, which displays it immediately. React then 'hydrates' this static HTML to make it interactive.
* **Pros**: Faster perceived load time (content visible quickly), excellent SEO, works without JS (basic content).
* **Cons**: Increased server load, more complex setup, potential for slower Time to Interactive if hydration is slow.
Most modern React frameworks like Next.js offer hybrid solutions, allowing you to choose CSR, SSR, or SSG (Static Site Generation) on a per-page basis.
Q19. How do you handle routing in React applications?
Routing in React applications is typically managed by a third-party library, with `React Router` being the most popular and widely used solution. React Router enables declarative routing, allowing you to define routes as components within your application's UI.
Key components of React Router:
1. **`BrowserRouter` / `HashRouter`**: The router component that wraps your entire application, providing the routing context.
2. **`Routes`**: A container that holds all individual `Route` components.
3. **`Route`**: Maps a URL path to a specific component that should be rendered when that path is active.
4. **`Link` / `NavLink`**: Components used for navigation. `Link` provides basic navigation, while `NavLink` adds styling capabilities for active links.
5. **`useParams` / `useNavigate`**: Hooks for accessing URL parameters or programmatically navigating.
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
function Home() { return <h2>Home Page</h2>; }
function About() { return <h2>About Page</h2>; }
Q20. What is the purpose of `React.memo`?
`React.memo` is a higher-order component (HOC) that can be used to optimize the rendering performance of functional components. It works by memoizing the component's render output and preventing it from re-rendering if its props have not changed.
When a parent component re-renders, its child components typically also re-render. `React.memo` performs a shallow comparison of the component's props with the previous props. If the props are shallowly equal, React skips rendering the component and reuses the last rendered result. This can lead to significant performance improvements for components that are expensive to render and frequently receive the same props.
It's important to use `React.memo` judiciously, as the shallow comparison itself has a cost. It's most effective for:
* Components that render frequently.
* Components that have complex rendering logic or many children.
* Components that receive stable props (e.g., primitive values, memoized objects/functions).
import React from 'react';
const MyPureComponent = React.memo(function MyPureComponent({ data }) {
console.log('MyPureComponent rendered');
return <div>{data}</div>;
});
// If 'data' prop doesn't change, 'MyPureComponent rendered' won't log on parent re-renders.
Q21. How do you optimize images in a React application?
Optimizing images is crucial for web performance. In a React application, several strategies can be employed:
1. **Image Compression**: Use tools (e.g., TinyPNG, ImageOptim) or build-time plugins (e.g., `imagemin-webpack-plugin`) to compress images without significant loss of quality.
2. **Responsive Images**: Serve different image sizes based on the user's device and screen resolution using `srcset` and `sizes` attributes with the `<img>` tag or the `<picture>` element. Libraries like `react-responsive-picture` can help.
3. **Modern Formats**: Utilize modern image formats like WebP or AVIF, which offer better compression than JPEG or PNG, with fallbacks for older browsers.
4. **Lazy Loading**: Defer loading of off-screen images until the user scrolls near them. This can be done with the `loading="lazy"` attribute on `<img>` tags or by using an `IntersectionObserver` API, often encapsulated in a custom hook or library (e.g., `react-lazyload`).
5. **Image CDN/Optimization Services**: Use services like Cloudinary, Imgix, or Next.js Image component (`next/image`) that handle image optimization, resizing, and format conversion on the fly.
6. **Placeholders**: Show a low-quality image placeholder or a blurred background while the high-resolution image loads.
Advanced Level
Q1. When would you use `useLayoutEffect` instead of `useEffect`?
Both `useEffect` and `useLayoutEffect` serve to run side effects after rendering, but they differ in their timing:
* **`useEffect`**: Fires *after* the browser has painted the screen. This is the most common and recommended hook for side effects because it doesn't block the browser's visual update, preventing potential performance issues and UI jank.
* **`useLayoutEffect`**: Fires *synchronously* after all DOM mutations but *before* the browser has a chance to paint. This means that any updates inside `useLayoutEffect` will block the browser's painting process until they are finished. If you read the DOM layout and then synchronously re-render, the user won't see an intermediate state.
You should use `useLayoutEffect` when you need to perform DOM measurements (e.g., getting element dimensions, scroll position) or make synchronous DOM modifications that need to be reflected before the browser paints. For example, positioning a tooltip based on another element's position, or adjusting scroll position. For most other side effects like data fetching, subscriptions, or updating the document title, `useEffect` is the correct choice.
Q2. Explain Error Boundaries in React.
Error Boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the entire application. They are a way to gracefully handle errors in the UI, rather than leaving users with a broken white screen.
An error boundary must be a class component and implement either or both of the lifecycle methods:
1. **`static getDerivedStateFromError(error)`**: Renders a fallback UI after an error has been thrown.
2. **`componentDidCatch(error, errorInfo)`**: Logs error information to an error reporting service.
Error boundaries only catch errors in the render phase, lifecycle methods, and constructors of the children tree. They do not catch errors in event handlers, asynchronous code (e.g., `setTimeout`, `Promise`), or in the error boundary itself.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Caught an error:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage:
// <ErrorBoundary><MyProblematicComponent /></ErrorBoundary>
Q3. What is Server-Side Rendering (SSR) in React and what are its benefits?
Server-Side Rendering (SSR) is a technique where the React application's initial HTML is generated on the server for each request, rather than entirely in the browser. The server renders the React components into an HTML string and sends it to the client. The client then receives the HTML, displays it instantly, and React 'hydrates' the static markup, attaching event handlers and making it interactive.
Benefits of SSR:
1. **Improved Performance/Perceived Performance**: Users see content sooner, as the browser receives a fully rendered page, reducing the blank screen time associated with client-side rendering.
2. **Better SEO**: Search engine crawlers can easily index the content since it's present in the initial HTML response, which is crucial for discoverability.
3. **Accessibility**: Provides a baseline HTML content for users with slower network connections or older devices.
4. **Faster Time to Interactive**: While client-side rendering might take longer to become interactive, SSR provides immediate content, improving the user experience.
Frameworks like Next.js simplify implementing SSR for React applications.
Q4. Describe Static Site Generation (SSG) with React frameworks like Next.js.
Static Site Generation (SSG) is a pre-rendering technique where HTML pages are generated at build time, rather than on each request (like SSR) or entirely in the browser (like CSR). With React frameworks like Next.js, you define your components, and during the build process, Next.js generates static HTML, CSS, and JavaScript files for each page.
When a user requests a page, the pre-generated HTML file is served directly from a CDN, leading to extremely fast load times and excellent performance. This approach is ideal for content-heavy sites (blogs, documentation, marketing pages) where data doesn't change frequently or can be fetched at build time.
Benefits of SSG:
1. **Superior Performance**: Pages are served as static assets, resulting in near-instant load times.
2. **Enhanced Security**: No server-side processing at runtime, reducing attack vectors.
3. **Excellent SEO**: All content is available in the HTML for crawlers.
4. **Scalability**: Easily scalable as static files can be served globally via CDNs.
Next.js uses functions like `getStaticProps` and `getStaticPaths` to fetch data and define dynamic routes at build time for SSG.
Q5. How do you implement code splitting and lazy loading in React?
Code splitting is a technique that breaks your application's bundle into smaller chunks, which can then be loaded on demand (lazy loading). This improves the initial load time of an application by only sending the necessary code to the user.
React provides `React.lazy` and `Suspense` for implementing code splitting at the component level:
1. **`React.lazy`**: This function lets you render a dynamic import as a regular component. It takes a function that returns a Promise, which resolves to a module with a default export containing a React component.
2. **`React.Suspense`**: This component allows you to specify a fallback UI (like a loading spinner) that will be displayed while the lazy-loaded component is being fetched and rendered. `Suspense` can wrap multiple lazy components.
import React, { lazy, Suspense } from 'react';
// Lazy load MyComponent
const MyLazyComponent = lazy(() => import('./MyComponent'));
function App() {
return (
<div>
<h1>Welcome</h1>
<Suspense fallback={<div>Loading component...</div>}>
<MyLazyComponent />
</Suspense>
</div>
);
}
Code splitting can also be applied at the route level using `React.lazy` with React Router.Q6. Discuss different state management patterns beyond Context API and Redux (e.g., Zustand, Jotai).
While Context API and Redux are popular, other state management libraries offer different paradigms and advantages, often aiming for simpler setup or better performance:
1. **Zustand**: A small, fast, and scalable bear-necessities state management solution. It's built on a simplified Flux-like pattern. You create a store using `create()`, and components can consume parts of the store using hooks. It avoids Context Providers entirely, making it feel more like a global `useState` and often leading to fewer re-renders than Context.
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}))
function Counter() {
const { count, inc } = useStore()
return <button onClick={inc}>{count}</button>
}
2. **Jotai**: A primitive and flexible state management library for React. It focuses on atom-based state management, where atoms are small, isolated pieces of state. Components subscribe to only the atoms they need, leading to highly optimized re-renders. It's often compared to Recoil but is typically smaller and has a simpler API.
These libraries often provide more granular control over re-renders and simpler boilerplate compared to Redux, making them excellent choices for many modern React applications, especially when Context API falls short for global state needs without introducing prop drilling or performance issues.Q7. How do you test React components effectively? Discuss different testing types.
Effective testing of React components involves a combination of different testing types to ensure reliability and maintainability:
1. **Unit Tests**: Focus on testing individual functions or small parts of a component in isolation. For React, this means testing a single component's rendering, state updates, and event handling. Libraries like Jest for testing framework and React Testing Library (RTL) for utilities are commonly used. RTL encourages testing components the way users would interact with them.
// Example with RTL
import { render, screen } from '@testing-library/react';
import Button from './Button';
test('renders a button with text', () => {
render(<Button>Click Me</Button>);
expect(screen.getByText(/click me/i)).toBeInTheDocument();
});
2. **Integration Tests**: Verify that several units or components work together as expected. For React, this might involve testing a small group of related components, ensuring data flows correctly between them, or testing a full feature (e.g., a form submission).
3. **End-to-End (E2E) Tests**: Simulate real user scenarios across the entire application, interacting with the deployed application in a browser. Tools like Cypress or Playwright are used for E2E testing. These tests ensure that the entire system, including backend integrations, functions correctly from a user's perspective.
Choosing the right balance of these tests is key for a robust testing strategy.Q8. Explain the concept of memoization in React and its implications.
Memoization is an optimization technique used to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. In React, memoization helps prevent unnecessary re-renders and re-computations, thereby improving performance.
React offers several tools for memoization:
1. **`React.memo`**: A Higher-Order Component that wraps functional components. It prevents a component from re-rendering if its props have not changed (performs a shallow comparison).
2. **`useMemo` hook**: Memoizes a computed value. It only re-calculates the value if its dependencies change.
3. **`useCallback` hook**: Memoizes a function instance. It only returns a new function instance if its dependencies change.
Implications:
* **Performance Boost**: Reduces rendering time and CPU usage, especially for complex components or expensive calculations.
* **Potential Overhead**: Memoization itself has a cost (memory for caching, comparison logic). Overuse can sometimes lead to *worse* performance if the comparison cost outweighs the re-computation/re-render cost.
* **Dependency Management**: Correctly managing the dependency arrays for `useMemo` and `useCallback` is crucial. Incorrect dependencies can lead to stale closures or unnecessary re-computations.
Q9. What are Concurrent Mode and Suspense in React?
Concurrent Mode (now referred to as Concurrent React) is a set of new features that enable React apps to be more responsive by making rendering interruptible. It allows React to work on multiple tasks concurrently, prioritizing urgent updates (like user input) over less urgent ones (like data fetching), without blocking the main thread.
**Suspense** is a feature built on top of Concurrent React that lets your components "wait" for something before rendering. It's primarily used for:
1. **Code Splitting**: With `React.lazy`, Suspense displays a fallback UI while a lazy-loaded component is being fetched.
2. **Data Fetching**: Eventually, Suspense will allow components to declaratively "suspend" rendering while data is being fetched, displaying a loading indicator until the data is ready. This is a more declarative approach than using `isLoading` states.
These features aim to make React applications feel faster and more fluid by improving the user experience during loading states and complex UI updates. While `React.lazy` and `Suspense` for code splitting are stable, the full data fetching capabilities of Suspense are still evolving with libraries like Relay or Next.js `fetch`.
Q10. How would you structure a large-scale React application?
Structuring a large-scale React application is crucial for maintainability and scalability. Common approaches include:
1. **Feature-based Structure**: Grouping files by feature rather than by type (e.g., `features/user/UserList.js`, `features/user/UserSlice.js`). This makes it easier to locate relevant files and manage feature-specific logic.
2. **Atomic Design/Component Library**: Organizing components into atoms, molecules, organisms, templates, and pages. Small, reusable components (atoms) form larger ones. This promotes reusability and a consistent design system.
3. **Domain-Driven Design**: Structuring around business domains, with clear boundaries for different parts of the application.
4. **Shared/Core Folder**: For common utilities, hooks, constants, and global styles that are used across multiple features.
5. **State Management**: Centralizing global state with libraries like Redux Toolkit, Zustand, or Jotai, often with a `store` or `state` directory.
6. **Routing**: A dedicated `router` folder for defining routes and associated components.
7. **Testing**: Placing tests alongside the code they test or in a separate `__tests__` directory.
8. **Clear Naming Conventions**: Consistent naming for files, components, and variables improves readability.
This structured approach helps manage complexity, facilitates collaboration, and makes it easier to onboard new developers.
Q11. What are the challenges and solutions for building accessible React applications?
Building accessible React applications ensures that users with disabilities can effectively use your product. Challenges and solutions include:
**Challenges**:
1. **Dynamic Content**: React's dynamic nature can make it difficult for screen readers to announce changes or focus correctly.
2. **Semantic HTML**: Over-reliance on `div`s and `span`s can degrade semantic meaning, making it harder for assistive technologies.
3. **Keyboard Navigation**: Ensuring all interactive elements are reachable and operable via keyboard.
4. **Focus Management**: Managing focus, especially in modals, popups, or routing changes.
5. **Lack of Awareness**: Developers might not be familiar with accessibility guidelines (WCAG).
**Solutions**:
1. **Semantic HTML**: Prioritize semantic HTML5 elements (`<button>`, `<nav>`, `<main>`) over generic `div`s. Use `React.Fragment` to avoid unnecessary wrappers.
2. **ARIA Attributes**: Use `aria-*` attributes (e.g., `aria-label`, `aria-describedby`, `role`) to provide additional semantic meaning where native HTML isn't sufficient.
3. **Keyboard Navigation**: Ensure all interactive elements are tabbable. Handle `onKeyDown` for custom interactions (e.g., Escape to close a modal).
4. **Focus Management**: Use `useRef` to manage focus programmatically, especially for modals (`focus` on open, `restore focus` on close, `trap focus` inside).
5. **Tooling & Linters**: Use tools like `eslint-plugin-jsx-a11y` and browser accessibility audits (Lighthouse) to catch issues early.
6. **Testing**: Include accessibility checks in your testing pipeline. Manual testing with screen readers is also crucial.
React supports standard HTML attributes, including `role` and `aria-*` attributes, making it easier to build accessible components.
Prepared by iCampusLink. 46 React interview questions.