Q1. What is React and why is it used?
React is an open-source JavaScript library developed by Facebook for building user interfaces with a component-based approach. It's designed for creating interactive, dynamic web applications with efficient rendering and state management. React simplifies the process of building complex UIs by breaking them down into small, reusable components that manage their own state and properties. The primary use of React is to create highly responsive and interactive web applications where the UI needs to update frequently based on changing data. React provides developers with tools and patterns for managing state, handling events, and rendering components efficiently. It's widely adopted in the industry because of its flexibility, strong community support, extensive ecosystem, and the ease of learning and implementing reactive user interfaces. React enables developers to build single-page applications (SPAs) where the UI is updated dynamically without full page reloads, providing a seamless user experience. React's component-based architecture promotes code reusability, maintainability, and scalability, making it ideal for large-scale applications. Understanding React is essential for modern front-end development, and mastering its core concepts is crucial for building professional web applications.
Q2. What is the Virtual DOM and how does it improve performance?
The Virtual DOM is a lightweight, in-memory representation of the real DOM maintained by React. It's a JavaScript object that mirrors the structure of the actual DOM tree but exists only in memory. When a component's state or props change, React creates a new Virtual DOM tree instead of directly manipulating the real DOM. React then compares the new Virtual DOM with the previous one, a process called diffing or reconciliation, to identify which elements have changed. Once differences are identified, React updates only those specific elements in the real DOM, rather than re-rendering the entire page. This approach significantly improves performance because directly manipulating the real DOM is slow and expensive, while manipulating the Virtual DOM is much faster since it's just a JavaScript object in memory. By batching updates and minimizing real DOM operations, React reduces the browser's reflow and repaint operations, which are the main performance bottlenecks. The Virtual DOM also provides a layer of abstraction that allows React to work with different rendering targets like React Native for mobile applications. Understanding the Virtual DOM is crucial for comprehending how React achieves high performance in complex applications. The Virtual DOM concept is one of React's key differentiators that makes it efficient for building interactive applications with frequent UI updates.
// React internally represents this:
const element = (
<div className="container">
<h1>Hello {name}</h1>
<p>Welcome to React</p>
</div>
);
// As this Virtual DOM object (simplified):
const vdom = {
type: 'div',
props: { className: 'container' },
children: [
{
type: 'h1',
props: {},
children: ['Hello ', name]
},
{
type: 'p',
props: {},
children: ['Welcome to React']
}
]
};
// When state changes, React compares old and new VDOM
// and updates only the changed parts in the real DOM
Q3. What is DOM diffing and reconciliation?
DOM diffing is the process of comparing two Virtual DOM trees to identify which elements have changed. Reconciliation is the process of updating the real DOM based on the differences found during diffing. When React re-renders a component, it creates a new Virtual DOM tree and compares it with the previous Virtual DOM tree, element by element. React uses an efficient algorithm that identifies the minimal set of changes needed to transform the old tree into the new tree. This algorithm works on the principle of comparing nodes at the same level first, then recursively comparing their children. If a node has changed, React marks it for update. If nodes have the same key, React assumes they represent the same component and preserves their state. The reconciliation process then takes these differences and applies updates to the real DOM, including adding new elements, removing elements, and updating existing elements with new properties or content. React also batches multiple updates together before applying them to the DOM, further improving performance by reducing reflows and repaints. The diffing algorithm has time complexity of O(n) because React uses heuristics rather than the optimal O(n³) algorithm, making it practical for large-scale applications. Understanding diffing and reconciliation is crucial for optimizing React application performance and debugging rendering issues. The ability to efficiently determine what changed allows React to update only the necessary parts of the DOM.
Q4. What is JSX and how does it work?
JSX is a syntax extension for JavaScript that allows you to write HTML-like code directly in your JavaScript files. JSX stands for JavaScript XML and looks like a combination of JavaScript and HTML. JSX is not valid JavaScript on its own; it must be transpiled (converted) to JavaScript function calls by a transpiler like Babel before the browser can execute it. When you write JSX like <div className="container"><h1>Hello</h1></div>, it's automatically converted to React.createElement() function calls. JSX provides a more readable and intuitive way to describe what the UI should look like compared to writing raw React.createElement() calls. JSX supports embedding JavaScript expressions within curly braces {}, allowing you to dynamically insert values, call functions, and use conditional logic within the markup. JSX is optional in React - you can write React applications using only createElement() calls, but JSX is the preferred approach because it's more intuitive and easier to read. JSX has some differences from HTML, such as using className instead of class, and camelCase for HTML attributes. JSX can return only a single root element, though you can use fragments <></> to group multiple elements without adding an extra DOM node. Understanding JSX is fundamental to React development, as it's the standard way to describe React UIs. JSX combines the power of JavaScript with the familiarity of HTML syntax.
Q5. Compare React with Angular and Vue.js.
React, Angular, and Vue.js are all popular front-end frameworks, but they differ significantly in their approach and philosophy. React is a library focused on the view layer, providing flexibility in choosing other tools for routing, state management, and HTTP requests. Angular is a complete, opinionated framework that includes everything out of the box - routing, HTTP client, form handling, and more. Vue.js is a progressive framework that starts simple for small projects but can grow to handle complex applications. React uses JSX, mixing HTML and JavaScript, which some developers love and others find unconventional. Angular uses TypeScript, HTML templates, and decorators, providing stricter structure and better tooling support. Vue.js uses single-file components with templates, scripts, and styles in one file, offering a middle ground between React's flexibility and Angular's structure. React has the largest community and ecosystem, with numerous third-party libraries. Angular has strong enterprise backing and is heavily used in large organizations. Vue.js has a growing community and is often praised for being easier to learn. React focuses on component reusability and functional programming patterns. Angular emphasizes convention over configuration and includes dependency injection. Vue.js provides a more intuitive API and shorter learning curve. All three use virtual DOM or similar concepts for efficient rendering. The choice between them depends on project requirements, team expertise, project size, and specific use cases. Understanding the differences helps in selecting the appropriate tool for your project.
Q6. What is the difference between functional and class components?
Functional components are JavaScript functions that return JSX and represent the modern, preferred approach in React. Class components are ES6 classes that extend React.Component and have been the traditional way to write components. Functional components are simpler and more concise, focusing purely on returning the UI based on props. Class components have access to lifecycle methods and internal state through this.state and this.setState(). Before React Hooks were introduced, class components were necessary for managing state and side effects, but hooks have largely eliminated this advantage. Modern React development predominantly uses functional components with hooks for state and side effect management. Functional components are easier to understand and test because they're just functions with no side effects beyond rendering. Class components require understanding the this keyword, component lifecycle, and binding methods, adding complexity. Functional components have better performance characteristics in some scenarios and support more advanced features like hooks for custom logic sharing. Class components still have some rarely-used features like error boundaries and getSnapshotBeforeUpdate, but these can be replicated with functional components using modern approaches. The React team recommends using functional components for new development. Understanding both types is important for maintaining existing codebases that may contain class components. Modern React development focuses almost exclusively on functional components with hooks.
// Functional Component
function Welcome({ name }) {
const [count, setCount] = React.useState(0);
return (
<div>
<h1>Hello, {name}</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
// Class Component
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<h1>Hello, {this.props.name}</h1>
<p>Count: {this.state.count}</p>
<button onClick={() =>
this.setState({ count: this.state.count + 1 })
}>
Increment
</button>
</div>
);
}
}
Q7. What are Props and State in React? What's the difference?
Props and State are both JavaScript objects that hold data influencing the output of a component, but they serve different purposes and have different characteristics. Props (properties) are read-only data passed from a parent component to a child component. Props flow downward in a unidirectional manner - a parent component can pass props to children, but children cannot modify props received from parents. Props make components reusable because the same component can be configured with different data. State is internal to a component and can be modified by that component itself using setState() or hooks like useState(). State is mutable and triggering state changes causes the component to re-render. Props are immutable from the child component's perspective - attempting to modify props will cause errors or unexpected behavior. State is local to a component and not directly accessible to parent or sibling components unless explicitly passed through props or callbacks. Props are useful for configuring component behavior and passing data down the component tree. State is useful for managing component-specific data that changes over time, like user input, animations, or fetched data. Components should lift state up to the nearest common ancestor when multiple components need to share state, then pass the state down through props. Understanding the distinction between props and state is fundamental to React development and proper data flow. Misusing these concepts is a common source of bugs in React applications.
// Child component using Props
function UserProfile({ name, age }) {
return (
<div>
<h1>{name}</h1>
<p>Age: {age}</p>
</div>
);
}
// Parent component managing State
function UserManager() {
const [user, setUser] = React.useState({
name: 'John',
age: 30
});
const handleAgeChange = (newAge) => {
setUser({ ...user, age: newAge });
};
return (
<div>
{/* Passing state as props to child */}
<UserProfile
name={user.name}
age={user.age}
/>
<button onClick={() => handleAgeChange(31)}>
Increment Age
</button>
</div>
);
}
Q8. What is one-way data flow in React?
One-way data flow means that data flows in only one direction - from parent components to child components through props. A parent component can pass data to its child components via props, but child components cannot directly modify the parent's data or pass data back to parents through props. This unidirectional flow creates a clear, predictable data architecture where the source of truth is the parent component. If a child component needs to communicate with its parent, it does so through callback functions passed as props. When the child calls these callback functions, they trigger state changes in the parent, which then flows back down to children as new props. This creates a cycle: data flows down as props, events flow up through callbacks, and state changes trigger new renders. One-way data flow makes applications easier to understand and debug because you always know where data is coming from and how it's being used. It prevents the complex, bidirectional data binding that can lead to hard-to-debug circular dependencies. React enforces this pattern by making props immutable - child components cannot modify props directly. This principle is often called the "unidirectional data flow" and is a core concept in React's architecture. Understanding and properly implementing one-way data flow is essential for building maintainable React applications. This pattern scales well in large applications, reducing complexity and making data flow predictable.
Q9. What is the React component lifecycle? Explain lifecycle methods in class components.
The React component lifecycle refers to the series of events that occur from the time a component is created until it's destroyed. Understanding the lifecycle helps you perform actions at specific times. Class components have three main lifecycle phases: Mounting (component is created and inserted into the DOM), Updating (component is re-rendered due to changes in props or state), and Unmounting (component is removed from the DOM). Key lifecycle methods in class components include constructor (called before component mounts, used for initialization), componentDidMount (called after component mounts, useful for API calls and side effects), componentDidUpdate (called after updates, useful for responding to prop or state changes), and componentWillUnmount (called before unmounting, useful for cleanup). Additional lifecycle methods include shouldComponentUpdate (controls if component should update), componentDidCatch (handles errors in rendering), and getSnapshotBeforeUpdate (captures DOM state before updates). Modern React development uses functional components with hooks instead of class lifecycle methods, as hooks provide a more flexible way to handle side effects. The useEffect hook replaces most lifecycle methods in functional components, offering better code organization and the ability to run multiple side effects. Understanding the lifecycle is important for managing state, fetching data, and cleaning up resources. Misunderstanding lifecycle methods is a common source of bugs in class component-based React applications.
Q10. What is the React component lifecycle with functional components and hooks?
In functional components, the lifecycle is managed through hooks, primarily useEffect. The useEffect hook runs after rendering and replaces componentDidMount, componentDidUpdate, and componentWillUnmount combined. By default, useEffect runs after every render, but you can control when it runs using the dependency array. An empty dependency array [] makes the effect run only once after initial mount, mimicking componentDidMount behavior. Specifying dependencies like [userId] makes the effect run only when those dependencies change. Omitting the dependency array makes the effect run after every render. useEffect can return a cleanup function, which runs when the component unmounts or before the effect runs again, mimicking componentWillUnmount behavior. This approach is more flexible and composable than class lifecycle methods - you can use multiple useEffect hooks in a single component, each handling a specific concern. The useLayoutEffect hook is similar to useEffect but runs synchronously after the DOM is mutated but before the browser paints, useful for measuring DOM elements. Modern functional components make managing side effects and lifecycle much simpler and more intuitive than class components. The hook-based approach encourages better code organization by keeping related logic together rather than splitting it across multiple lifecycle methods. Understanding how to use useEffect effectively is crucial for managing side effects in functional components.
Q11. What are React Hooks? List important hooks and their uses.
React Hooks are functions that let you use state and other React features in functional components. Introduced in React 16.8, hooks eliminated the need to convert to class components for state management and side effects. useState hook manages component state in functional components, accepting an initial value and returning the current state and a function to update it. useEffect hook handles side effects like API calls, subscriptions, and timers, running after the component renders. useContext hook accesses context values without wrapping components in consumers, simplifying context consumption. useRef hook creates a persistent mutable reference to DOM elements or values that don't cause re-renders. useMemo hook memoizes expensive computations, recalculating only when dependencies change, improving performance. useCallback hook memoizes function references, useful for preventing unnecessary re-renders of child components that receive functions as props. useReducer hook manages complex state logic with a reducer function, useful for state machines and complex state transformations. useLayoutEffect hook is like useEffect but runs synchronously before the browser paints, useful for DOM measurements. useId hook generates unique IDs for accessibility and form elements. Custom hooks are functions that use other hooks and encapsulate component logic for reuse across components. Hooks follow two rules: they must be called at the top level of components (not conditionally), and they can only be called from React functions (components or custom hooks). Understanding hooks is essential for modern React development, as they're the preferred way to manage state and side effects in functional components.
Q12. Explain useState hook in detail with examples.
The useState hook allows functional components to have internal state. It takes an initial value as an argument and returns an array with two elements: the current state value and a function to update that state. When you call the state setter function with a new value, React re-renders the component with the updated state. useState can be called multiple times in a component to manage different pieces of state. Each call to useState creates an independent state variable. The initial value can be a simple value, an object, or the result of a function (lazy initialization). When the state setter is called with the same value, React skips the re-render, providing a performance optimization. You can pass an updater function to the state setter instead of a new value, which receives the current state and returns the new state. This is useful when the new state depends on the previous state. State updates in React are batched for performance - multiple setState calls in the same function are batched into a single re-render. useState state should be treated as immutable - you should never modify state directly but instead create new values. For complex state objects, you often need to spread the object to create a new reference with updated properties. Understanding useState is fundamental to using React hooks effectively, as it's the most commonly used hook for managing component state.
Q13. Explain useEffect hook with dependency array and cleanup.
The useEffect hook runs side effects after the component renders. It takes a callback function and an optional dependency array. Without a dependency array, useEffect runs after every render, which can cause performance issues if the effect is expensive. An empty dependency array [] makes useEffect run only once after the initial render, useful for initialization effects like API calls. Specifying dependencies like [userId, postId] makes useEffect run only when those dependencies change. React compares dependencies using Object.is(), so ensure array/object references are stable to avoid unnecessary effect runs. useEffect can return a cleanup function that runs before the effect runs again and when the component unmounts, useful for cleanup like canceling requests or removing event listeners. Failing to provide proper cleanup can cause memory leaks and bugs. The dependency array should include all values from the component scope that change over time. Missing dependencies is a common source of bugs. useEffect is called synchronously after painting, though you should generally avoid expensive operations that block painting. useLayoutEffect is available for cases where you need to run effects before the browser paints. Multiple useEffect hooks can be used in a single component to handle different concerns. Proper use of useEffect requires understanding when to use it, what dependencies to specify, and how to properly clean up. Understanding useEffect is crucial for managing side effects in functional components and preventing bugs.
Q14. What is useContext hook? How do you use it to avoid prop drilling?
The useContext hook provides a way to access context values directly within components without wrapping them in a Consumer component. Context is a way to pass data through the component tree without passing props manually at every level, solving the "prop drilling" problem. Prop drilling occurs when you pass props through many intermediate components just to reach a component that needs them. useContext hook simplifies context consumption by allowing you to call useContext(ContextObject) to get the current context value directly. You must first create a context using React.createContext(), which returns a Context object. Then wrap your component tree with the Provider component from that context, passing the value through the value prop. Components within the provider can use useContext to access that value. useContext is cleaner than using Context.Consumer component because it keeps logic in the component body rather than mixing JSX with consumer components. When the context value changes, all components using useContext with that context are automatically re-rendered, even if their props didn't change. useContext makes it easy to implement global state for themes, authentication, language preferences, and other application-level data. Combining useContext with useReducer creates a simple but effective state management solution without additional libraries. Performance can be a concern if context values change frequently, as it will cause all consuming components to re-render. Proper use of useContext requires understanding context principles and knowing when to use it versus other state management approaches.
Q15. Explain useRef hook and when to use it.
The useRef hook returns a mutable reference object whose value is stored in the ref.current property. Unlike state, updating a ref doesn't cause a re-render, making it useful for values that don't directly affect the UI. useRef is commonly used to access DOM elements directly, like focusing an input field, managing focus, triggering animations, or selecting text. You attach a ref to a DOM element using the ref attribute, then access the DOM node via ref.current. useRef can also store any mutable value that persists across renders without causing re-renders, useful for storing previous state, timer IDs, or other mutable values. Refs should be used sparingly because they break the declarative nature of React - directly manipulating DOM elements bypasses React's rendering model. Most UI updates should be handled through state and props rather than direct DOM manipulation. useRef is useful for integrating with third-party libraries that need direct DOM access, like jQuery plugins or video players. useRef with callbacks can be used to track DOM node changes and perform actions when nodes are added or removed. Refs are created fresh on every render in the component's instance, so each component instance has its own refs. useRef doesn't notify you when its contents change - you should use useEffect if you need to react to ref changes. Proper use of useRef requires understanding when direct DOM access is appropriate and when to use state instead. Overusing refs is a sign that you might need to reconsider your component architecture.
Q16. What are useMemo and useCallback hooks? When should you use them?
useMemo and useCallback are performance optimization hooks that memoize values and functions to prevent unnecessary recalculations and re-renders. useMemo memoizes the result of an expensive computation and recalculates only when dependencies change. It takes a function and a dependency array, returning the memoized value. This is useful for expensive computations like filtering large lists or complex calculations that would otherwise run on every render. useCallback memoizes a function reference and returns the same function instance across renders unless dependencies change. It's useful for passing callback functions to optimized child components that use React.memo - when the callback reference stays the same, child components don't re-render unnecessarily. Both hooks should be used judiciously because they add overhead and can actually hurt performance if overused. Premature optimization with useMemo and useCallback is a common mistake that adds complexity without benefit. You should only use these hooks when you have identified actual performance problems, such as expensive computations or frequent unnecessary re-renders of optimized children. Creating new dependencies for memoization itself should be memoized, which can lead to convoluted code. useCallback can be replaced with a stable function reference by memoizing dependencies or moving functions outside the component. useMemo should only be used when the computation is actually expensive - not for every variable. React.memo for components and memoizing callbacks only makes sense if the component has expensive computations or prevents expensive child re-renders. Understanding when and how to use these optimization hooks is important for writing performant React applications.
Q17. What are custom hooks? How do you create them?
Custom hooks are JavaScript functions that use one or more React hooks and encapsulate stateful logic for reuse across components. Custom hooks allow you to extract component logic into reusable functions, similar to higher-order components or render props but with cleaner syntax. Creating a custom hook involves writing a function that starts with "use" (by convention), uses other hooks, and returns values or functions needed by components. Custom hooks can call other hooks, allowing you to compose complex logic from simpler hooks. Each call to a custom hook creates independent state and effects, not shared across different components calling the same hook. Custom hooks make it possible to share stateful logic between components without render props or higher-order components. Examples of custom hooks include useFetch for data fetching, useForm for form management, useLocalStorage for local storage persistence, and useWindowSize for responsive design. Custom hooks should follow the rules of hooks - they should only be called at the top level and only from React components or other custom hooks. Custom hooks reduce code duplication and improve code organization by grouping related logic together. Testing custom hooks is simpler than testing components because hooks are just functions. Custom hooks can be packaged into libraries for sharing across projects. Understanding custom hooks is essential for writing scalable React applications with clean, maintainable code. Custom hooks are a powerful tool for abstracting and reusing complex component logic.
Q18. What are Higher-Order Components (HOC) and Render Props patterns?
Higher-Order Components (HOC) and Render Props are patterns for sharing component logic across components before hooks were introduced. An HOC is a function that takes a component and returns a new component with additional props or behavior. HOCs allow you to reuse component logic, enhance components with additional functionality, or wrap components for cross-cutting concerns. For example, a withTheme HOC might wrap a component and provide theme-related props. Render Props is a pattern where a component accepts a function as a child (render prop) and calls that function with state or props, allowing the parent to render content with that data. The render prop function receives values from the parent and returns JSX to be rendered. Examples include components that handle data fetching and provide data through a render prop. Both patterns achieve code reuse and logic abstraction but have drawbacks. HOCs can create wrapper hell with deeply nested component hierarchies, making debugging difficult. Render Props can lead to callback hell and deeply nested JSX. Both patterns can make understanding data flow difficult. React Hooks have largely replaced HOCs and Render Props as the preferred way to share stateful logic. Custom hooks are cleaner, more composable, and easier to understand than HOCs or Render Props. Modern React development focuses almost exclusively on hooks. However, understanding HOCs and Render Props is still valuable for understanding existing codebases and API design patterns. Some third-party libraries still use HOCs or Render Props, requiring knowledge to use them effectively. Understanding these patterns helps in recognizing when to use custom hooks instead.
Q19. What is React.memo? How does it improve performance?
React.memo is a higher-order component that memoizes a functional component, preventing unnecessary re-renders when props haven't changed. It wraps a component and performs a shallow comparison of the new and previous props - if they're the same, it returns the cached component instance instead of re-rendering. React.memo is useful for components that receive the same props frequently and have expensive render logic. By preventing re-renders, it can significantly improve performance in component trees with deep hierarchies or frequent parent re-renders. React.memo performs shallow comparison by default - if props are objects or arrays, it compares the references, not the contents. To handle complex prop comparisons, you can provide a custom comparison function as the second argument. React.memo only memoizes props comparison - it doesn't memoize internal state changes or context updates. Memoizing a component doesn't prevent updates from context changes that the component uses. React.memo should be used strategically - memoizing every component is unnecessary overhead. Memoization only helps if the component receives the same props frequently, otherwise the comparison itself becomes overhead. Common use cases include List items that render frequently without prop changes, buttons or icons that never change, and expensive components with static props. Combining React.memo with useCallback for callback props ensures that memoization is effective. Overusing React.memo is a common mistake that adds complexity without performance benefit. Understanding when React.memo is beneficial requires profiling and measuring actual performance improvements.
// Component that will be memoized
const UserCard = ({ user, onUpdate }) => {
console.log('UserCard rendered');
return (
<div>
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<button onClick={onUpdate}>Update</button>
</div>
);
};
// Memoize the component
const MemoizedUserCard = React.memo(UserCard);
// With custom comparison
const CustomMemoCard = React.memo(
UserCard,
(prevProps, nextProps) => {
// Return true if props are equal (skip render)
return prevProps.user.id === nextProps.user.id;
}
);
// Usage
function UserList() {
const [count, setCount] = React.useState(0);
const user = { id: 1, name: 'John', email: 'john@example.com' };
const handleUpdate = React.useCallback(() => {
console.log('Update clicked');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Rerender ({count})
</button>
{/* Memoization prevents re-render unless props change */}
<MemoizedUserCard
user={user}
onUpdate={handleUpdate}
/>
</div>
);
}
Q20. What is the difference between useMemo and React.memo?
useMemo and React.memo serve different purposes and operate at different levels. React.memo is a higher-order component that memoizes an entire component, preventing re-renders when props don't change. useMemo is a hook that memoizes a value inside a component, preventing recalculation when dependencies don't change. React.memo is applied at the component level - you wrap a component definition with React.memo() to prevent unnecessary re-renders of that component. useMemo is applied at the value level - you use it inside a component to memoize expensive computations. React.memo compares all props using shallow comparison (or custom comparison function) and skips the entire component re-render if props are the same. useMemo recomputes the value only when dependencies change, even if the component re-renders. useMemo is often used in combination with React.memo to be most effective - without memoizing callback functions passed as props, React.memo memoization becomes ineffective because callback references change on every render. React.memo is for optimizing entire components, while useMemo is for optimizing specific values within components. You can use React.memo for components that receive expensive static props, and useMemo for expensive computations that happen during render. Using both together requires care to avoid over-complicating code. Understanding when to use each tool is important for proper performance optimization. Both should be used judiciously only when profiling shows actual performance issues.
Q21. What are controlled and uncontrolled components?
Controlled components are components where React state controls the form input values. The input's value comes from state, and onChange handlers update the state, making React the "single source of truth." Controlled components provide precise control over input values and make it easy to implement features like validation, disabling inputs, or changing values programmatically. Uncontrolled components manage their own state internally, like traditional HTML form elements. The input's value comes directly from the DOM, and you access the current value through refs. Uncontrolled components are simpler for straightforward form scenarios where you don't need real-time validation or programmatic control. The React documentation recommends controlled components for most use cases because they integrate better with React's declarative paradigm. Controlled components make it easier to implement form validation, conditional rendering, and dynamic form manipulation. Uncontrolled components are useful for integrating with non-React libraries that expect to manage DOM directly, or for forms where you only need the final submitted value. Converting between controlled and uncontrolled introduces bugs - you must choose one pattern and stick with it for each input. Some inputs like file inputs are always uncontrolled because the browser doesn't allow programmatic value changes for security reasons. The choice between controlled and uncontrolled components affects how you handle form state, validation, and submission. Understanding the trade-offs helps in building forms that are both easy to manage and performant. Modern form libraries like React Hook Form provide hooks for managing both controlled and uncontrolled components efficiently.
Q22. What are React Router basics? How do you set up routing?
React Router is a library for client-side routing in React applications, enabling navigation between different views without full page reloads. React Router v6 (the current major version) uses components like BrowserRouter, Routes, Route, Link, and Navigate for routing. BrowserRouter wraps the entire application and provides routing context. Routes component contains Route definitions, and only one matching route renders at a time. Route defines a path and the component to render when that path matches. Link component enables navigation without page reloads, updating the URL and the currently rendered component. The newer way uses useNavigate hook in components to programmatically navigate. Route can have nested routes using the children prop, creating hierarchical routing structures. Dynamic segments like /users/:id create routes with parameters accessible through useParams hook. Route can protect routes using element property or by wrapping routes with guard components. Query strings are handled separately from path parameters and accessed through useSearchParams hook. Routes can define redirects using Navigate component or by catching unmatched routes with path="*". React Router provides hooks like useLocation for accessing current location, useHistory/useNavigate for navigation, and useParams for accessing route parameters. Setting up routing typically involves wrapping your app with BrowserRouter at the root, defining routes in the Routes component, and using Link or navigate for navigation. Modern React Router v6 is hooks-based, making it much cleaner than earlier versions. Understanding routing is essential for building single-page applications with multiple views.
Q23. What are nested routes and lazy loading in React Router?
Nested routes allow you to define routes within routes, creating hierarchical navigation structures. In React Router v6, nested routes are defined using the children property of a parent Route or by placing routes within a parent route's element. The parent route's element must include an Outlet component that displays the matched child route component. Nested routes enable complex layouts where different sections of the page can have different sub-routes, like an admin dashboard with nested sections for users, posts, and settings. Lazy loading improves performance by splitting your code and loading route components only when needed. React.lazy() and Suspense enable lazy loading of route components. You wrap a lazy-loaded component with Suspense and provide a fallback UI to display while the code is loading. Implementing lazy loading requires minimal changes - replace direct imports with lazy() and wrap the Routes with Suspense. When a user navigates to a lazy-loaded route, the JavaScript for that route is downloaded and evaluated, adding a slight delay. Using a loading indicator improves user experience during lazy loading. Lazy loading is most beneficial for large applications where code splitting significantly reduces initial bundle size. Combining nested routes with lazy loading creates scalable, modular application architectures. Lazy loading works transparently - components don't need to know they're lazy-loaded. Error boundaries can catch errors in lazy-loaded components and display error messages. Modern bundlers and frameworks automatically optimize lazy loading. Understanding nested routes and lazy loading is crucial for building scalable React applications with good performance.
Q24. What is Redux? Explain the Redux pattern and its core concepts.
Redux is a predictable state management library that provides a centralized, immutable store for application state. Redux follows a strict pattern to manage state changes, making applications more predictable and easier to debug. The core concepts are Store (the single source of truth containing all application state), Actions (plain objects describing what happened), and Reducers (pure functions that update state based on actions). The Redux flow is unidirectional: components dispatch actions describing user interactions, reducers process actions and return new state, and the store notifies subscribers of state changes, triggering component re-renders. Selectors are functions that extract specific state slices, allowing components to subscribe to relevant data. Redux requires all state to be immutable - you create new state objects instead of modifying existing ones. Redux Middleware allows handling asynchronous actions and side effects, with redux-thunk being a popular middleware for async actions. Redux DevTools provide powerful debugging capabilities, allowing you to see every action, inspect state changes, and time-travel through state history. Redux is particularly useful for large applications with complex state, teams with multiple developers, and applications requiring extensive debugging and testing. Redux adds boilerplate compared to simpler state management solutions like Context API. Redux Toolkit simplifies Redux setup by providing utilities for common patterns. Redux is increasingly competing with alternative state management solutions like Recoil, Zustand, and Jotai. Understanding Redux is valuable for maintaining existing applications and for choosing appropriate state management solutions.
Q25. What are Redux actions, reducers, and selectors?
Redux Actions are plain JavaScript objects that describe what happened. Every action has a type property that describes the action, and optionally a payload containing data. Actions are dispatched by components, UI events, or middleware to signal that state should change. The action type is typically a string constant to prevent typos and make refactoring easier. Actions should be serializable and not contain functions or side effects. Reducers are pure functions that take the current state and an action, and return a new state. Reducers should be pure - the same input always produces the same output with no side effects. Reducers must not mutate the original state; instead, they return a new state object with the changes. Reducers are combined in the store configuration to handle different state slices. Each reducer is responsible for a specific part of the state tree. Reducers should handle the actions they care about and return the state unchanged for actions they don't handle. Selectors are functions that extract specific pieces of state from the store. Selectors allow components to subscribe to specific state slices rather than the entire state. Using selectors makes it easy to refactor state structure without changing components. Reselect library provides memoized selectors that improve performance by not recalculating when inputs haven't changed. Action creators are functions that create action objects, reducing boilerplate and making actions easier to test. Action types are often organized as constants to enable refactoring and prevent typos. Understanding actions, reducers, and selectors is essential for working with Redux effectively. Proper organization of these elements is crucial for building maintainable Redux applications.
Q26. What is Redux Toolkit and how does it simplify Redux?
Redux Toolkit is the official, recommended way to write Redux applications. It simplifies Redux development by providing utilities that reduce boilerplate and make common patterns easier. Redux Toolkit includes the store setup utilities, action creators, and reducers all combined into slices. A slice combines related reducers, actions, and action creators into a single unit, making organization cleaner. createSlice function generates action creators and reducers automatically from a simple configuration object. createAsyncThunk handles asynchronous operations more cleanly than raw redux-thunk, providing pending, fulfilled, and rejected action types automatically. Redux Toolkit uses Immer internally, allowing you to write "mutating" code in reducers that's actually creating new immutable state. This makes reducer code more intuitive and less error-prone. configureStore sets up the Redux store with good defaults, automatically including Redux DevTools middleware and other commonly used middleware. Redux Toolkit is now the recommended approach by the Redux team and is generally preferred over raw Redux. Redux Toolkit reduces the amount of boilerplate required and makes Redux feel less heavy-weight. Learning Redux Toolkit is recommended for anyone using Redux in modern applications. Redux Toolkit works seamlessly with React Redux hooks like useSelector and useDispatch. Most new Redux applications use Redux Toolkit rather than raw Redux. Understanding Redux Toolkit is becoming as important as understanding Redux fundamentals.
Q27. What is React Query? How does it differ from Redux for state management?
React Query (now TanStack Query) is a library specifically designed for managing server state and caching in React applications. Unlike Redux, which manages all application state, React Query focuses specifically on server-side data, handling fetching, caching, synchronization, and updating. React Query automatically handles common patterns like deduplication (preventing duplicate requests), stale data management, background refetching, and error handling. It provides hooks like useQuery for fetching data and useMutation for mutations, making data-fetching code simpler and more intuitive. React Query automatically manages request deduplication - multiple components requesting the same data will share a single request. It intelligently manages stale data and can refetch data in the background to keep it fresh. React Query provides powerful caching strategies and automatic garbage collection of unused cached data. Redux is better for application state that doesn't come from a server, while React Query excels at managing server state. Using React Query reduces the amount of Redux boilerplate for data fetching. React Query works well alongside Redux, Context API, or other state management solutions. React Query handles many patterns that were previously painful in Redux. The separation between server state (React Query) and application state (Redux or Context) makes applications cleaner and more maintainable. Modern React applications often use React Query for server state and Context API or lighter state management for application state. Understanding when to use React Query versus other state management tools is valuable for building efficient data-driven applications.
Q28. What is Context API vs Redux? When should you use which?
Context API is a built-in React feature for managing state and avoiding prop drilling, while Redux is a separate library for centralized state management. Context API is simpler and requires less boilerplate, making it suitable for smaller applications or specific use cases like themes or authentication. Context API is part of React, requiring no external dependencies, while Redux requires installation and setup. Context API can cause performance issues when context values change frequently, as all consuming components re-render even if they only use a small part of the context. Redux provides better performance optimizations through selectors and memoization. Redux provides time-travel debugging and detailed action tracking, making it excellent for complex applications needing extensive debugging. Redux requires more boilerplate and initial setup but provides a consistent, predictable pattern. Context API is easier to learn and understand, making it suitable for developers new to state management. Context API is good for relatively static data like themes, user authentication, or application configuration. Redux is better for complex, frequently-updated state that multiple components depend on. Many applications use both - Context API for simple global state and Redux for complex application state. Modern alternatives like Zustand and Recoil offer middle grounds between Context API simplicity and Redux power. The choice depends on application complexity, team expertise, and specific requirements. For small applications or specific state management needs, Context API often suffices. For large, complex applications, Redux provides better scalability and debugging.
Q29. What are Code Splitting and React.lazy? How do they improve performance?
Code splitting is a technique where you split your JavaScript bundle into smaller chunks that are loaded on demand rather than loading everything upfront. This reduces the initial bundle size and improves application startup time. React.lazy enables code splitting for React components, allowing you to lazy-load components that aren't needed initially. React.lazy takes a dynamic import and returns a component that can be rendered with Suspense. Dynamic imports use the import() function, which returns a promise that resolves to the module when loaded. Suspense component wraps lazy-loaded components and displays a fallback UI while the component is loading. Code splitting can happen at different levels: route-level (loading different routes separately), component-level (loading heavy components on demand), or library-level (lazy-loading heavy libraries). Route-based code splitting is most common and effective - each route loads only the code needed for that route. Component-level code splitting is useful for heavy components used in multiple places. Code splitting works automatically with modern bundlers like Webpack, Parcel, and Vite when using dynamic imports. The browser automatically downloads split chunks when needed, and module federation allows sharing code between independently deployed applications. Code splitting significantly improves Time to Interactive (TTI) and First Contentful Paint (FCP) metrics, improving perceived performance. Proper code splitting requires measuring and monitoring bundle sizes to identify splitting opportunities. Code splitting is essential for large applications to maintain acceptable performance as codebases grow. Understanding code splitting and React.lazy is crucial for building performant large-scale React applications.
Q30. What is Suspense in React and how do you use it?
Suspense is a React component that allows you to handle loading states declaratively while code or data is being loaded. Suspense wraps components and displays a fallback UI until the wrapped component is ready to render. Suspense works with React.lazy for code splitting, displaying a loading indicator while the lazy component is being loaded. Suspense also works with concurrent features and data fetching libraries like React Query for showing loading states while data is being fetched. When a component inside Suspense throws a promise (indicating it's waiting for data), Suspense catches it and displays the fallback UI. Once the promise resolves, Suspense renders the component. Suspense enables cleaner code by separating loading state management from component logic. Multiple Suspense boundaries can be nested, allowing fine-grained control over which parts of the UI show loading states. Suspense fallback can be any React element - typically a spinner, skeleton screen, or placeholder content. Suspense integrates with error boundaries to handle errors during loading. Suspense is particularly powerful when combined with concurrent rendering features for better perceived performance. Resources can be preloaded before navigation to minimize loading delays. Suspense requires components to properly handle asynchronous operations by throwing promises. Suspense is still considered experimental for data fetching, though it's production-ready for code splitting. Future React versions will expand Suspense capabilities for broader use cases. Understanding Suspense is important for handling loading states in modern React applications, particularly with concurrent features.
Q31. What are Error Boundaries? How do you implement them?
Error Boundaries are React components that catch JavaScript errors anywhere in the component tree and display a fallback UI instead of crashing the entire application. Error Boundaries are implemented using class components with lifecycle methods componentDidCatch and getDerivedStateFromError. Error Boundaries catch errors during rendering, in lifecycle methods, and in constructors, but not for asynchronous code, event handlers, or server-side rendering. You should use try-catch for event handlers instead. getDerivedStateFromError updates state when an error is caught, triggering a re-render with the fallback UI. componentDidCatch is called after an error is caught and is useful for logging errors, reporting to error tracking services, or showing error details. Error Boundaries can be placed at different levels - at the root level to catch all errors, or around specific component trees to catch errors in isolated sections. Creating multiple Error Boundary components at different levels allows graceful degradation where one component error doesn't crash the entire app. Error boundaries improve user experience by showing friendly error messages instead of blank screens. Error Boundaries don't prevent errors - they just catch and handle them gracefully. Functional components don't support error boundaries directly, but you can create a custom hook-based approach or use third-party libraries. React team is working on adding error boundary support to functional components in the future. Error boundaries are essential for production applications to handle unexpected errors gracefully. Understanding how to implement and use Error Boundaries is important for building robust React applications.
Q32. What are React 18 features? Explain concurrent rendering, useTransition, and useDeferredValue.
React 18 introduces several powerful features for improving user experience and application performance. Concurrent rendering allows React to interrupt long renders to handle high-priority updates, like user input, keeping the application responsive. This is a fundamental architectural change enabling better performance without developer effort. useTransition hook allows you to mark state updates as non-urgent, letting React prioritize other updates. It returns a pending boolean indicating if a non-urgent update is in progress, and a startTransition function to wrap state updates. This is useful for handling expensive computations like filtering large lists without blocking user input. useDeferredValue hook defers updating a value until more urgent updates are processed. It's useful for keeping derived state in sync with the main state without blocking urgent updates. useDeferredValue is similar to useTransition but operates on values rather than state updates. Automatic batching improves performance by batching state updates that happen in the same event, reducing renders. This applies to all state updates including async operations. Strict Mode now runs effects twice during development to help identify issues with cleanup code. These features enable building responsive applications where high-priority updates (user input) are always prioritized over low-priority updates (background data fetches). React 18 features are backward compatible - existing applications work without changes. Properly using React 18 features requires understanding prioritization and when to defer updates. React 18 significantly improves the developer experience and application performance. Understanding React 18 features is increasingly important for modern React development.
Q33. What are React Server Components (RSC)?
React Server Components are an experimental feature allowing components to be rendered on the server and sent to the client as serialized data. Server Components execute only on the server and don't send JavaScript to the client, reducing bundle size. Server Components can access server-only resources like databases and secrets without exposing them to clients. Regular React components (Client Components) continue to work as before but must be marked with the "use client" directive. Server Components enable building applications with zero-JavaScript overhead for non-interactive content, improving performance significantly. Server Components can render Client Components, but Client Components cannot render Server Components. This creates a clear separation between server and client code. Server Components are particularly useful for rendering static content, list items, or data-heavy pages without JavaScript overhead. Next.js 13+ has built-in support for Server Components through the App Router. Server Components change how you think about React development, shifting some logic back to the server. They work well with streaming to send content to clients as it renders. Server Components are still experimental and not yet widely adopted outside Next.js. Understanding Server Components is important for future React development and for using modern Next.js effectively. Server Components represent a significant evolution in how React applications are structured and deployed.
Q34. What are performance optimization techniques in React?
Performance optimization in React involves multiple strategies targeting different aspects of rendering and execution. Code splitting reduces initial bundle size by loading code on demand. React.lazy enables automatic code splitting at the component level. Route-based code splitting is the most effective approach. Using React.memo prevents unnecessary re-renders of components with unchanged props. useMemo and useCallback memoize values and functions to avoid recalculations. However, memoization should be used strategically only for expensive computations. TrackBy functions (similar to list keys) help React identify which list items have changed. Virtual lists render only visible items instead of entire large lists, improving scrolling performance. Lazy loading images and deferring non-critical content improves perceived performance. Using production builds enables tree-shaking and minification, reducing bundle size significantly. Profiling with Chrome DevTools and React DevTools identifies actual bottlenecks. Measure-driven optimization ensures effort is spent on actual problems. Using useCallback with event handlers prevents creating new functions on every render. Proper dependency management in useEffect prevents unnecessary effect runs. Moving state down to where it's needed reduces unnecessary re-renders of parent components. Using proper key props in lists helps React identify changes correctly. Preloading likely-to-be-needed resources improves perceived performance. Debouncing and throttling expensive operations like window resize or scroll. Using web workers for heavy computations keeps the main thread responsive. React 18 concurrent rendering features enable better prioritization of updates. Understanding and applying appropriate optimization techniques is essential for building performant React applications. Premature optimization should be avoided - measure first, then optimize actual bottlenecks.
Q35. What is testing in React? Explain unit, integration, and E2E testing.
Testing in React involves multiple testing levels to ensure code quality and prevent regressions. Unit testing tests individual components or functions in isolation, checking that they behave correctly with various inputs. Jest and Vitest are popular testing frameworks for unit testing in JavaScript and React. Integration testing tests how multiple components work together to ensure they integrate properly. Component integration tests verify that components pass data correctly and respond to interactions. End-to-end (E2E) testing tests the entire application from user perspective, simulating real user interactions. Cypress and Playwright are popular E2E testing tools that run tests in a real browser. Testing best practices include testing behavior rather than implementation details, avoiding testing React internals, and focusing on what users see and do. React Testing Library encourages testing components the way users would interact with them rather than testing internals. Tests should be fast, independent, and reliable to provide value. Good test coverage includes happy paths, edge cases, and error scenarios. Snapshot testing captures component output and compares future renders, useful for detecting unintended changes. However, snapshot tests require careful review to prevent test rot. Testing hooks requires special libraries like @testing-library/react-hooks for testing hooks in isolation. Test-driven development (TDD) writes tests before implementation, leading to better design. Continuous integration runs tests automatically on every commit, catching regressions early. Understanding different testing approaches and tools helps in choosing appropriate strategies for applications. Well-tested React applications are more maintainable and have fewer bugs in production.
Q36. What is TypeScript with React? What are the benefits?
TypeScript is a superset of JavaScript that adds static type checking, enabling you to catch errors before runtime. Using TypeScript with React provides significant benefits including better IDE support, improved code documentation through type annotations, and catching bugs early during development. TypeScript helps prevent common React mistakes like passing wrong prop types or accessing undefined properties. React components can be typed to ensure props are correct, catching errors at development time rather than runtime. TypeScript enables better refactoring because type changes propagate through the codebase, catching places that need updates. Generics allow creating flexible, reusable components and functions with type safety. TypeScript interfaces and types document component APIs clearly, improving code maintainability. Components can be exported with proper types for better library usability. Redux with TypeScript provides type-safe state management and dispatch. React hooks can be typed to ensure return types are correct. Error handling becomes more explicit with typed error types. Learning TypeScript has a learning curve, but the benefits for larger applications far outweigh the cost. TypeScript is increasingly the standard in production React applications. Modern tools like Vite and Create React App provide good TypeScript support. TypeScript compilation adds a build step but modern tooling makes this transparent. TypeScript integrates seamlessly with React and popular libraries. Using TypeScript helps maintain large codebases and enables confident refactoring. Understanding TypeScript is increasingly important for professional React development.
Q37. What are styled-components and CSS-in-JS?
Styled-components is a popular library for writing CSS in JavaScript, enabling scoped styling directly in React components. CSS-in-JS is a broader concept of writing styles in JavaScript files rather than separate CSS files. Styled-components provide component-scoped styles, eliminating CSS class naming conflicts. Styles are created using template literals and can be combined with component props for dynamic styling. Dynamic styling based on props enables theming and conditional styling without additional CSS. Styled-components generate unique class names automatically, preventing style conflicts in large applications. Server-side rendering is well-supported with automatic critical CSS extraction. Styled-components provide better developer experience with hot reloading and error messages pointing to actual component code. Other CSS-in-JS libraries include Emotion, Tailwind CSS, and others with different trade-offs. CSS-in-JS approaches make styling more component-centric and easier to maintain. Bundle size overhead exists compared to traditional CSS but is often acceptable. Performance is generally good, though there are cases where traditional CSS performs better. Styled-components work well with TypeScript for typed style props. Styled-components integrate seamlessly with modern development workflows. Testing styled-components requires understanding how they generate class names. CSS-in-JS solutions like styled-components are increasingly popular in modern React development, particularly for component libraries.
Q38. How do you handle forms in React? Explain form libraries like React Hook Form.
Form handling in React can be done through controlled components (managing state for each input) or uncontrolled components (managing form data through refs). Controlled components require updating state on every input change, which can be verbose for large forms. React Hook Form simplifies form handling by managing form state with minimal re-renders. React Hook Form uses uncontrolled components internally but provides controlled-like API through hooks. The useForm hook registers form fields, handles submission, and manages validation. The register function connects input fields to the form, and watch watches specific field values. React Hook Form provides flexible validation including built-in validators and custom validation functions. Form submission is handled through the handleSubmit function, which validates before calling the submission handler. React Hook Form significantly reduces boilerplate and improves performance compared to fully controlled forms. Formik is another popular form library providing validation, error messages, and submission handling. Form libraries abstract away repetitive form logic, making code more maintainable. Validation can happen on blur, change, or submission depending on requirements. Error messages should be displayed near relevant fields for good UX. Form reset and initialization is handled by form libraries. Complex forms with dynamic fields benefit most from form libraries. Understanding form handling patterns helps in choosing between libraries and custom solutions. Modern form libraries enable building complex forms efficiently in React applications.
Q39. What are best practices for React development?
React development best practices help maintain code quality and build scalable applications. Keep components small and focused on a single responsibility. Lift state up to the nearest common ancestor when multiple components need to share state. Use proper key props in lists to help React identify which items have changed. Avoid inline function definitions in render to prevent creating new functions every render. Use functional components and hooks instead of class components for new development. Use proper dependency arrays in useEffect to prevent unnecessary effect runs and memory leaks. Implement proper error boundaries to handle errors gracefully. Use TypeScript for better type safety and IDE support. Write tests for important functionality and critical paths. Use React DevTools for debugging and understanding component tree. Avoid deeply nested components - extract components to avoid callback hell. Use meaningful variable and component names for better code readability. Avoid prop drilling by using Context API or state management for shared state. Profile performance with Chrome DevTools to identify actual bottlenecks. Use production builds for performance testing, not development builds. Separate concerns - keep business logic in custom hooks or services. Use memoization strategically only for expensive computations. Follow ESLint rules and style guides for consistent code. Keep components pure - same inputs should always produce same outputs. Use proper folder structure for scalability. Document components with clear prop documentation. Review code and maintain high standards through peer review. Continuous learning and staying updated with React developments is important.
Q40. What is Next.js and how does it enhance React?
Next.js is a React framework that provides a comprehensive solution for building production-ready applications. It provides built-in server-side rendering (SSR), static site generation (SSG), and incremental static regeneration (ISR). Server-side rendering improves SEO and initial page load performance by rendering pages on the server. Static site generation pre-builds pages at build time, providing excellent performance for static content. File-based routing automatically creates routes from the file structure, eliminating the need for router configuration. API routes allow building backend endpoints without separate servers, simplifying full-stack development. Automatic code splitting and optimization for bundle size without manual configuration. Built-in image optimization component reduces image size and improves performance. TypeScript support is built-in with no configuration needed. Middleware support enables request transformation and authentication. Database integration is simplified through ORM support. Deployment is straightforward on Vercel or other platforms. Next.js 13+ introduces the App Router with React Server Components, fundamentally changing how applications are structured. The Pages Router (older approach) is still supported for backward compatibility. Next.js handles many concerns that would otherwise require additional configuration or libraries. Next.js is increasingly the standard for production React applications, particularly for SEO-critical applications. Learning Next.js is becoming as important as learning React itself. Next.js makes building full-stack applications with React significantly easier. Understanding Next.js enables building modern, production-ready React applications efficiently.
Q41. What is server-side rendering (SSR) and static site generation (SSG) in Next.js?
Server-side rendering (SSR) generates HTML on the server for each request, sending fully rendered HTML to the client. SSR improves SEO because search engines receive complete HTML with content. SSR provides better initial page load performance because browsers receive rendered HTML instead of blank pages. Each request is rendered fresh, providing always up-to-date content. SSR uses more server resources compared to static generation. Static site generation (SSG) pre-builds HTML pages at build time, creating static files served to clients. SSG provides excellent performance because pages are already built and just served directly. SSG is ideal for content that doesn't change frequently or changes predictably. SSG supports incremental static regeneration (ISR) to update pages without rebuilding the entire site. ISR allows configuring revalidation time after which pages are regenerated on the next request. Hybrid rendering uses both SSG and SSR - statically generate pages and refresh them based on changes. Client-side rendering (CSR) loads JavaScript and renders in the browser, traditional SPA approach. ISR provides the best of both worlds - fast static pages with automatic updates. Choose SSR for frequently changing content where freshness matters. Choose SSG for mostly static content that benefits from performance. Choose CSR for highly interactive, user-specific content. Combining approaches in the same application provides flexibility. Understanding when to use each rendering strategy is crucial for building performant Next.js applications. Proper rendering strategy selection significantly impacts SEO, performance, and server resource usage.
Q42. How do you optimize images in React and Next.js?
Image optimization is crucial for web performance, as images are often the largest assets. Next.js provides the Image component that automatically optimizes images. The Image component handles responsive images, format selection, and lazy loading automatically. WebP format is used for modern browsers while fallbacks support older browsers. Image dimensions must be specified to prevent layout shifts and reserve space. Priority prop marks images as high-priority to load them early, useful for above-the-fold content. Lazy loading is enabled by default, loading images as they come into view. Placeholder blur provides a blurred placeholder while the full image loads. Responsive images are served at appropriate sizes based on device screen size. Srcset is automatically generated to serve optimal image sizes. CSS-in-JS solutions may impact performance but optimizations minimize impact. Image CDNs like Cloudinary provide automatic optimization and delivery. Sprite sheets and SVG icons reduce HTTP requests for icons. Converting images to WebP format saves bandwidth without quality loss. Lazy loading images with Intersection Observer API improves perceived performance. Deferring non-critical images until user scrolls improves initial load. Using appropriate image dimensions prevents browser scaling overhead. Compressing images reduces file size without visible quality loss. Responsive images serve different sizes for different screen sizes. Using CSS background images strategically can improve performance. Understanding image optimization techniques significantly improves application performance. Images are often the largest assets, making optimization high impact.
Q43. What are environment variables and secrets in React and Next.js?
Environment variables allow configuring applications for different environments (development, staging, production) without code changes. Environment variables are typically stored in .env files and loaded at runtime. In Next.js, environment variables must be prefixed with NEXT_PUBLIC_ to be exposed to the browser. Variables without this prefix are server-only and not exposed to client-side code. Environment variables for sensitive data like API keys should never be committed to version control. .env files should be added to .gitignore to prevent accidentally committing secrets. Secrets should be managed through CI/CD systems, environment variable services, or secret management tools. Accessing environment variables in Next.js is done through process.env.VARIABLE_NAME. Client-side access is limited to NEXT_PUBLIC_ prefixed variables for security. Server-side environment variables in API routes or server components are not exposed to clients. Docker and deployment platforms provide native environment variable support. .env files should never contain actual secrets in production. Using environment variable validation ensures required variables are present. Type-safe environment variables can be achieved through validation libraries. Next.js provides .env.local for local overrides and .env.production for production values. Properly managing environment variables is crucial for security and application configuration. Understanding environment variable patterns is essential for deploying secure applications.
Q44. How do you handle authentication and authorization in React?
Authentication verifies user identity, while authorization determines what authenticated users can do. Common authentication methods include username/password, OAuth/OIDC, and API keys. JWT (JSON Web Tokens) are commonly used for maintaining authenticated sessions. Tokens are typically stored in localStorage or cookies and sent with API requests. Cookies have the advantage of automatic inclusion in requests but are vulnerable to CSRF. localStorage has the advantage of being unaffected by CSRF but requires manual inclusion in requests. Refreshing tokens involves requesting new tokens when existing ones expire. Logout removes tokens from storage and clears user state. Protected routes check authentication before allowing navigation. Route guards can redirect unauthenticated users to login pages. Combining authentication with roles and permissions enables authorization. Context API or state management stores user information and authentication status. API interceptors automatically include tokens in requests without boilerplate. HTTPS is mandatory for secure authentication to prevent token interception. Same-Site cookies prevent CSRF attacks in cookie-based authentication. Authorization checks should happen on both frontend and backend. Frontend authorization improves UX, while backend authorization provides security. Auth0, Firebase, and similar services simplify authentication implementation. Building custom authentication requires careful attention to security. Understanding common authentication vulnerabilities and how to prevent them is crucial. Proper authentication and authorization implementation is essential for secure applications.
Q45. What is API integration in React? How do you fetch and handle data?
API integration involves making HTTP requests to backend services to fetch and send data. The fetch API is the native browser API for making HTTP requests. Third-party libraries like axios provide better error handling and convenience. React Query simplifies data fetching with caching, deduplication, and error handling. useQuery hook fetches data with loading, error, and data states. useMutation hook handles POST, PUT, DELETE requests with loading and error states. Error handling should distinguish between network errors and API errors. Request interceptors can add authentication headers and transform requests. Response interceptors can transform responses and handle errors globally. Request debouncing prevents excessive API calls during rapid interactions. Caching improves performance by avoiding repeated requests for same data. Pagination and infinite scroll improve performance for large datasets. Real-time updates through WebSockets or polling keep data fresh. Optimistic updates improve perceived performance by updating UI before server response. Rollback on error restores state if requests fail. Race condition prevention ensures latest request response is used. Request cancellation prevents handling responses from outdated requests. Rate limiting prevents overwhelming servers. Proper error messages improve user experience. Loading states indicate to users that requests are in progress. Type-safe API calls with TypeScript improve reliability. API documentation helps understand endpoints and requirements. Proper API integration patterns are crucial for building data-driven applications. Understanding different approaches and tools helps in choosing appropriate solutions.
Q46. What are browser APIs and how do you use them in React?
Browser APIs provide access to browser functionality like local storage, geolocation, camera, and more. localStorage API provides persistent key-value storage persisting across sessions. sessionStorage is similar to localStorage but data clears when the page closes. Geolocation API accesses user location if permission is granted. Camera and microphone APIs enable building video/audio applications. Clipboard API enables copying and pasting data programmatically. Notification API sends desktop notifications to users. Service Workers enable offline functionality and background processing. Web Workers offload heavy computations to background threads. IndexedDB provides client-side database for complex data storage. File API enables reading and processing files selected by users. Fetch API makes HTTP requests, fundamental for data fetching. WebSocket API enables real-time bidirectional communication. In React, useEffect is commonly used to access browser APIs. useRef accesses DOM elements directly when necessary. Custom hooks encapsulate browser API usage for reusability. Permissions API checks and requests permissions before using restricted APIs. Feature detection checks if APIs are supported before using them. Polyfills provide fallbacks for older browsers lacking API support. Browser APIs should be accessed carefully to avoid side effects. Testing browser APIs requires mocking in unit tests. Server-side rendering requires care when accessing browser APIs. Performance impacts should be considered for heavy browser API usage. Understanding available browser APIs enables building powerful applications. Proper use of browser APIs enhances user experience and application capabilities.
Q47. What is WebSocket integration in React for real-time updates?
WebSocket provides full-duplex communication between client and server, enabling real-time updates. Unlike HTTP which is request-response based, WebSocket maintains a persistent connection. WebSocket is ideal for chat applications, live notifications, real-time dashboards, and collaborative tools. In React, WebSocket connections are typically established in useEffect. A cleanup function closes the connection when the component unmounts. WebSocket messages are handled through event listeners like onmessage. Sending data to server uses the send method with JSON-stringified data. Libraries like Socket.io provide abstraction over WebSocket with fallbacks for older browsers. React hooks can wrap WebSocket logic for reusability across components. Custom hooks handle connection management, message sending, and disconnection. Multiple components can share WebSocket connection through Context API or state management. Reconnection logic handles temporary disconnections automatically. Message handlers update component state when messages arrive. Type-safe WebSocket messages with TypeScript improve reliability. Error handling manages connection failures gracefully. Performance considerations include debouncing updates and limiting message frequency. Server-sent events (SSE) provide one-way communication as an alternative for simpler scenarios. Rate limiting prevents overwhelming servers with too many messages. Proper WebSocket usage enables building responsive, real-time React applications. Understanding WebSocket patterns and libraries is important for real-time features.
Q48. How do you build accessible React applications?
Accessibility ensures applications are usable by everyone, including people with disabilities. Semantic HTML uses proper elements (button, nav, article) improving screen reader comprehension. ARIA attributes provide additional context for screen readers when semantic HTML isn't sufficient. Keyboard navigation ensures all functionality is accessible using keyboard. Focus management ensures focus moves logically through interactive elements. Color contrast ensures text is readable for people with color blindness. Alt text for images provides context for screen reader users. Proper heading hierarchy (h1, h2, h3) structures content for screen readers. Form labels properly associate with inputs for screen reader users. Error messages should be clear and associated with form fields. Animations should respect prefers-reduced-motion for users sensitive to motion. Font sizes should be readable and support zooming. Proper button types (button vs div styled as button) improve accessibility. ARIA live regions announce dynamic content changes to screen readers. Skip links allow jumping over navigation to main content. Testing with keyboard navigation reveals accessibility issues. Screen reader testing ensures content is understandable. Automated accessibility checkers like axe identify many issues. Manual testing catches issues automation misses. WebAIM resources provide comprehensive accessibility guidance. WCAG 2.1 AA is the standard for accessible web content. Building accessible applications benefits all users, not just those with disabilities. Accessibility should be built in from the start, not added later. Understanding accessibility best practices ensures inclusive applications.
Q49. What is the component composition pattern in React?
Component composition is creating complex components from simpler components, enabling code reuse and scalability. React's component-based architecture is fundamentally about composition. Container components handle state and logic, presentational components render UI. Composition enables reusing components in different contexts. Props drilling (passing props through multiple levels) can be avoided through composition and Context API. Children prop allows components to render arbitrary content, enabling flexible composition. Slot pattern uses children prop to provide flexible content areas. Compound components share state between components that work together. Composition over inheritance means using composition rather than extending components. Render props enable sharing behavior by passing render functions. HOCs wrap components with additional functionality. Provider pattern provides values to deeply nested components. Separation of concerns means each component has a single responsibility. Building small, focused components makes composition easier. Props interface documentation clarifies how components are composed. Predictable component behavior enables confident composition. TypeScript enables type-safe composition. Testing composed components ensures parts work together. Performance in composed components depends on proper memoization. Large component trees can impact performance if not optimized. Virtual DOM helps mitigate composition performance issues. Understanding composition patterns enables building maintainable, scalable applications. Proper use of composition is fundamental to React development.
Q50. What are common React anti-patterns and how do you avoid them?
Anti-patterns are common mistakes that result in poorly designed, hard-to-maintain code. Mutating state directly instead of using setState breaks React's reactivity - always create new state. Using array index as key causes issues when list items are reordered or removed. Passing object literals as props creates new objects on every render, breaking memoization. Not cleaning up subscriptions and event listeners causes memory leaks. Using setState in render causes infinite render loops. Not handling errors gracefully provides poor user experience. Deeply nested ternary operators make code unreadable - extract to helper functions. Prop drilling multiple levels deep indicates need for Context or state management. Storing derived state causes synchronization issues - calculate when needed. Missing dependency arrays in useEffect causes stale data and bugs. Inline function definitions in render or props create new functions every render. Not using TypeScript misses catching errors at development time. Testing implementation details instead of behavior makes tests brittle. Over-engineering simple solutions adds unnecessary complexity. Not measuring performance results in premature optimization. Ignoring accessibility makes applications unusable for many people. Not handling loading and error states frustrates users. Mixing async and sync logic in reducers causes hard-to-debug issues. Not following React conventions makes code hard for others to understand. Avoiding these common anti-patterns leads to better, more maintainable React code. Learning from mistakes of others helps in writing better code. Code review helps catch anti-patterns early. Understanding why patterns are anti-patterns helps internalize best practices.