React interview questions

·

12 min read

  1. What are the differences between class components and functional components in React?

  2. What is the significance of React keys, and when should they be used?

  3. Explain the component lifecycle methods in React.

  4. What are React hooks, and how do they work?

  5. What is the difference between state and props in React?

  6. What are Higher-Order Components (HOCs), and why are they used?

  7. What is the Context API, and how is it used in React?

  8. Explain the concept of controlled and uncontrolled components in React.

  9. How do you handle forms and form inputs in React?

  10. What is React.Fragment, and what are its benefits?

  11. How do you handle performance optimizations in React?

  12. How do you manage state in a React application? Discuss local component state, lifting state up, and using state management libraries like Redux.

  13. What is the Virtual DOM, and how does it work in React?

  14. Explain the purpose of React.PureComponent and how it differs from React.Component.

  15. How do you implement error boundaries in a React application?

  16. What is prop drilling, and how can it be avoided?

  17. Describe React's synthetic event system.

  18. Explain the concept of component composition in React.

  19. What is React's server-side rendering (SSR), and why is it important?

  20. How do you use code-splitting and lazy-loading in a React application?

  21. what is hydration in React ?

  22. How to pass data from a child component to a parent component?

Answers

  1. Class vs functional components: Class components use ES6 classes and have access to the full component lifecycle and state. Functional components are simpler, using functions to define components. With React Hooks, functional components can now use state and lifecycle features as well.

  2. React keys: Keys are unique identifiers assigned to elements in a list to help React identify and efficiently manage changes to the DOM. Keys should be unique within the scope of the list, often using IDs from the data.

     const numbers = [1, 2, 3, 4, 5];
     const listItems = numbers.map((number) => <li key={number.toString()}>{number}</li>);
    
  3. Component lifecycle methods: Lifecycle methods are special methods that get called during the different phases of a component's life, such as mounting, updating, and unmounting. Some common methods include componentDidMount, componentDidUpdate, and componentWillUnmount.

     | Class Component      | Hook Equivalent                             |
     |----------------------|---------------------------------------------|
     | componentDidMount    | useEffect with empty dependency array ([])  |
     | componentDidUpdate   | useEffect with dependency array containing  |                   
     |                      | state/prop values to watch for changes      |                              
     | componentWillUnmount | useEffect with a cleanup function returned  |
     | shouldComponentUpdate| React.memo or useMemo hook                  |
    
  4. React hooks: Hooks are functions that let you use state and lifecycle features in functional components. Some common hooks include useState, useEffect, and useContext.

  5. State vs props: State is the internal data that a component manages and can change over time. Props are external data passed down from parent components, and are read-only within the receiving component.

  6. Higher-Order Components (HOCs): HOCs are functions that take a component and return a new component with additional props or behavior. They are used for code reuse and to apply common logic across multiple components.

    1. First, create the withUser HOC:

       import React from 'react';
      
       function withUser(WrappedComponent) {
         return function WithUserComponent(props) {
           const user = {
             id: 1,
             name: 'John Doe',
             email: 'john.doe@example.com'
           };
      
           return <WrappedComponent {...props} user={user} />;
         };
       }
      
    2. Next, create a functional component that will be wrapped by the HOC

       import React from 'react';
      
       function UserProfile(props) {
         const { user } = props;
      
         return (
           <div>
             <h1>User Profile</h1>
             <p>ID: {user.id}</p>
             <p>Name: {user.name}</p>
             <p>Email: {user.email}</p>
           </div>
         );
       }
      
    3. Now, wrap the UserProfile component with the withUser HOC:

       const EnhancedUserProfile = withUser(UserProfile);
      
    4. Finally, use the enhanced component in your application:

       import React from 'react';
       import ReactDOM from 'react-dom';
       import { EnhancedUserProfile } from './EnhancedUserProfile';
      
       function App() {
         return (
           <div>
             <h1>React HOC Example</h1>
             <EnhancedUserProfile />
           </div>
         );
       }
      
       ReactDOM.render(<App />, document.getElementById('root'));
      
  7. Context API: The Context API provides a way to pass data through the component tree without having to pass props down manually at every level. It is useful for sharing global state or data, such as theme settings or user authentication.

    1. First, create a ThemeContext.js file that defines the context and its provider:

       import React, { createContext, useState } from 'react';
      
       const ThemeContext = createContext();
      
       const ThemeProvider = ({ children }) => {
         const [theme, setTheme] = useState('light');
      
         const toggleTheme = () => {
           setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
         };
      
         return (
           <ThemeContext.Provider value={{ theme, toggleTheme }}>
             {children}
           </ThemeContext.Provider>
         );
       };
      
       export { ThemeContext, ThemeProvider };
      
    2. Next, wrap your application with the ThemeProvider in the index.js file:

       import React from 'react';
       import ReactDOM from 'react-dom';
       import App from './App';
       import { ThemeProvider } from './ThemeContext';
      
       ReactDOM.render(
         <ThemeProvider>
           <App />
         </ThemeProvider>,
         document.getElementById('root')
       );
      
    3. Now, create the App.js file that uses the ThemeContext:

       import React, { useContext } from 'react';
       import { ThemeContext } from './ThemeContext';
      
       const App = () => {
         const { theme, toggleTheme } = useContext(ThemeContext);
      
         const appStyle = {
           backgroundColor: theme === 'light' ? '#fff' : '#333',
           color: theme === 'light' ? '#333' : '#fff',
           minHeight: '100vh',
           display: 'flex',
           flexDirection: 'column',
           justifyContent: 'center',
           alignItems: 'center',
         };
      
         return (
           <div style={appStyle}>
             <h1>React Context API Example</h1>
             <p>Current theme: {theme}</p>
             <button onClick={toggleTheme}>Toggle Theme</button>
           </div>
         );
       };
      
       export default App;
      
  8. Controlled vs uncontrolled components: Controlled components manage form input values through React state and have their values controlled by React. Uncontrolled components manage their own state, and their values are read directly from the DOM.

    1. First, create a ControlledInput component as a controlled component:

       import React, { useState } from 'react';
      
       const ControlledInput = () => {
         const [value, setValue] = useState('');
      
         const handleChange = (event) => {
           setValue(event.target.value);
         };
      
         return (
           <div>
             <label>
               Controlled Input:
               <input type="text" value={value} onChange={handleChange} />
             </label>
             <p>Entered Value: {value}</p>
           </div>
         );
       };
      
       export default ControlledInput;
      

      In this example, the ControlledInput component manages its input value using the value state. The handleChange function updates the state when the input value changes.

    2. Next, create an UncontrolledInput component as an uncontrolled component:

       import React, { useRef } from 'react';
      
       const UncontrolledInput = () => {
         const inputRef = useRef();
      
         const handleClick = () => {
           alert(`Entered Value: ${inputRef.current.value}`);
         };
      
         return (
           <div>
             <label>
               Uncontrolled Input:
               <input type="text" ref={inputRef} />
             </label>
             <button onClick={handleClick}>Show Value</button>
           </div>
         );
       };
      
       export default UncontrolledInput;
      

      In this example, the UncontrolledInput component doesn't manage the input value using state. Instead, it uses a ref to access the input value directly when the button is clicked.

    3. Finally, create an App component to display the controlled and uncontrolled components:

       import React from 'react';
       import ControlledInput from './ControlledInput';
       import UncontrolledInput from './UncontrolledInput';
      
       const App = () => {
         return (
           <div>
             <h1>Controlled vs Uncontrolled Components</h1>
             <ControlledInput />
             <br />
             <UncontrolledInput />
           </div>
         );
       };
      
       export default App;
      
  9. Handling forms: In React, forms are typically managed as controlled components, with input values stored in state and updated using event handlers (e.g., onChange). Form submission is handled with a separate event handler (e.g., onSubmit).

  10. React.Fragment: A React.Fragment is a lightweight container for grouping elements without adding an extra node to the DOM. It helps to reduce unnecessary DOM nesting and improve performance.

    use <> </> or React.fragment

  11. Performance optimizations: Performance can be optimized using techniques like debouncing, memoization, using React.memo for functional components, React.PureComponent for class components, or implementing shouldComponentUpdate.

    1. Avoid inline functions and objects in JSX: Creating functions and objects inline in JSX causes unnecessary re-renders because they are considered new references on each render. Instead, define functions and objects outside the JSX or use useCallback and useMemo hooks to memoize them.

       // Bad
       <button onClick={() => handleClick(id)}>Click me</button>
      
       // Good
       const handleClick = useCallback(() => {
         // Do something with id
       }, [id]);
       <button onClick={handleClick}>Click me</button>
      
    2. Use PureComponent or React.memo: PureComponent (for class components) and React.memo (for functional components) perform a shallow comparison of the component's props and prevent re-rendering if the props haven't changed.

       // Functional component
       const MyComponent = (props) => {/*...*/}
       export default React.memo(MyComponent);
      
       // Class component
       class MyComponent extends React.PureComponent {/*...*/}
      
    3. Optimize lists with keys: When rendering lists, always provide a unique key for each item. This helps React efficiently update the DOM when items are added, removed, or reordered.

       {items.map(item => (
         <ListItem key={item.id} item={item} />
       ))}
      
    4. Use virtualized lists: For rendering large lists, consider using virtualization libraries like react-window or react-virtualized, which only render the visible items in the viewport, improving performance.

    5. Optimize state updates with the functional form of setState: When updating state based on previous state, use the functional form of setState to ensure that the updates are correctly applied.

       setState(prevState => prevState+1);
      
    6. Minimize expensive computations with useMemo: Use the useMemo hook to memoize the results of expensive computations, preventing unnecessary recalculations when the dependencies haven't changed.

       const expensiveResult = useMemo(() => {
         return expensiveComputation(value);
       }, [value]);
      
    7. Debounce or throttle input events: To limit the number of events triggered by fast input actions, like typing or scrolling, use debounce or throttle techniques. Libraries like lodash provide debounce and throttle functions that can be easily integrated into your components.

    8. Use lazy loading and code splitting: Take advantage of React.lazy and React.Suspense to lazy load components and split your code into smaller chunks. This reduces the initial bundle size and speeds up the loading of your application.

       import React, { lazy, Suspense } from 'react';
       const MyComponent = lazy(() => import('./MyComponent'));
      
       const App = () => (
         <div>
           <Suspense fallback={<div>Loading...</div>}>
             <MyComponent />
           </Suspense>
         </div>
       );
      
    9. Optimize context updates: When using context to share state, minimize unnecessary re-renders by splitting the context into multiple smaller contexts or using a state management library like Redux or MobX.

    10. Profile and measure performance: Use the React DevTools and browser profiling tools to measure and analyze the performance of your application. Identify bottlenecks and optimize accordingly.

  12. State management: State can be managed using local component state, lifting state up to a common ancestor component, or using state management libraries like Redux or MobX for more complex applications.

  13. Virtual DOM: The Virtual DOM is an in-memory representation of the actual DOM that React uses to track changes in the component tree. It allows React to perform efficient updates to the actual DOM by calculating the minimal set of changes (diffing) and applying them in batches.

  14. React.PureComponent: React.PureComponent is a base class for class components and React.memo for functional components that performs a shallow comparison of props and state It helps optimize rendering performance by preventing unnecessary updates.

    import React from 'react';
    
    const MyComponent = (props) => {
      console.log('Rendering MyComponent');
    
      return (
        <div>
          <h1>{props.title}</h1>
          <p>{props.description}</p>
        </div>
      );
    };
    
    const MemoizedMyComponent = React.memo(MyComponent);
    
    export default MemoizedMyComponent;
    
  15. Error boundaries: Error boundaries are React components that catch and handle errors in their child component tree. They are created using a class component with the componentDidCatch or getDerivedStateFromError lifecycle methods.

  16. Prop drilling: Prop drilling is the process of passing props down through multiple layers of components, even if some intermediate components don't use the props. It can be avoided using the Context API or other state management solutions.

  17. Synthetic event system: React's synthetic event system is a cross-browser wrapper around the browser's native event system, providing a consistent API and improved performance. It reuses event objects and pools them to reduce garbage collection overhead.

  18. Component composition: Component composition is a technique in React where components are built by combining smaller, reusable components. It promotes code reusability, maintainability, and separation of concerns.

  19. Server-side rendering (SSR): SSR is the process of rendering React components on the server and sending the resulting HTML to the client, which can then be hydrated into a fully interactive React app. SSR is important for improving initial page load performance, search engine optimization (SEO), and social media sharing previews.

  20. Code-splitting and lazy-loading: Code-splitting is the process of breaking up a large bundle of JavaScript code into smaller chunks, which can be loaded on demand. Lazy-loading is the practice of loading these chunks only when they are needed, such as when a user navigates to a specific route. These techniques improve the performance of a React app by reducing the initial bundle size and loading time. React's React.lazy() and React.Suspense can be used to implement code-splitting and lazy-loading.

    Here's an example of how you can use code splitting in a React application:

    1. Create a new component called MyComponent in a separate file named MyComponent.js:

       // MyComponent.js
       import React from 'react';
      
       const MyComponent = () => {
         return <div>This is a lazily loaded component!</div>;
       };
      
       export default MyComponent;
      
    2. Use React.lazy and React.Suspense in your main component:

       // App.js
       import React, { lazy, Suspense } from 'react';
      
       const MyComponent = lazy(() => import('./MyComponent'));
      
       const App = () => {
         const [showComponent, setShowComponent] = React.useState(false);
      
         return (
           <div>
             <button onClick={() => setShowComponent(true)}>Load MyComponent</button>
      
             {showComponent && (
               <Suspense fallback={<div>Loading...</div>}>
                 <MyComponent />
               </Suspense>
             )}
           </div>
         );
       };
      
       export default App;
      
  21. Hydration is the process of attaching event listeners to the server-rendered HTML once the JavaScript has been loaded on the client-side. It enables server-rendered applications to become interactive without re-rendering the entire DOM.

    1. Create the parent component:

       // ParentComponent.js
       import React, { useState } from 'react';
       import ChildComponent from './ChildComponent';
      
       const ParentComponent = () => {
         const [childData, setChildData] = useState('');
      
         const handleChildData = (data) => {
           setChildData(data);
         };
      
         return (
           <div>
             <h1>Parent Component</h1>
             <p>Received from child: {childData}</p>
             <ChildComponent onSendData={handleChildData} />
           </div>
         );
       };
      
       export default ParentComponent;
      

      In the parent component, we define a callback function handleChildData that sets the state with the data received from the child component. We pass this callback function as a prop named onSendData to the ChildComponent.

    2. Create the child component:

       // ChildComponent.js
       import React, { useState } from 'react';
      
       const ChildComponent = (props) => {
         const [inputValue, setInputValue] = useState('');
      
         const handleChange = (e) => {
           setInputValue(e.target.value);
         };
      
         const handleClick = () => {
           props.onSendData(inputValue);
         };
      
         return (
           <div>
             <h2>Child Component</h2>
             <input type="text" value={inputValue} onChange={handleChange} />
             <button onClick={handleClick}>Send to Parent</button>
           </div>
         );
       };
      
       export default ChildComponent;
      

In the ChildComponent, we have an input field and a button. When the user types in the input field, the state inputValue is updated. When the user clicks the "Send to Parent" button, the handleClick function is called, which in turn calls the onSendData prop (the handleChildData function from the parent component) with the current inputValue. This way, the data is passed from the child component to the parent component.