ReactJS Interview Questions: Intermediate Level Part 2
Are you ready to level up your ReactJS interview preparation? In this article, we’ll explore some intermediate-level questions that delve deeper into React concepts and best practices. Let’s dive in!
1. Describe the Flux architecture pattern and its relationship with React.
Flux is an application architecture pattern developed by Facebook for building client-side web applications. It emphasizes unidirectional data flow, making it easier to reason about state changes and manage complex data flows. In Flux, data flows in a single direction: from the dispatcher to the stores and then to the views.
The relationship between Flux and React is symbiotic. While React provides the view layer for building user interfaces, Flux provides the architecture for managing data flow within the application. React components can serve as the view layer in a Flux architecture, rendering UI components based on the data provided by Flux stores.
Actions:
// actions.js
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const addTodo = (text) => ({
type: ADD_TODO,
payload: { text },
});
export const toggleTodo = (id) => ({
type: TOGGLE_TODO,
payload: { id },
});
Dispatcher:
// dispatcher.js
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
Stores:
// reducers.js
import { ADD_TODO, TOGGLE_TODO } from './actions';
const initialState = {
todos: [],
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, { id: Date.now(), text: action.payload.text, completed: false }],
};
case TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id ? { ...todo, completed: !todo.completed } : todo
),
};
default:
return state;
}
};
export default rootReducer;
Views (React Components):
// TodoList.js
import React from 'react';
import { connect } from 'react-redux';
import { addTodo, toggleTodo } from './actions';
const TodoList = ({ todos, addTodo, toggleTodo }) => {
const handleAddTodo = () => {
const text = prompt('Enter todo:');
if (text) {
addTodo(text);
}
};
return (
<div>
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</li>
))}
</ul>
</div>
);
};
const mapStateToProps = state => ({
todos: state.todos,
});
export default connect(mapStateToProps, { addTodo, toggleTodo })(TodoList);
2. What are Higher Order Components (HOCs) in React? Give an example.
Higher Order Components (HOCs) are a pattern in React for reusing component logic. They are functions that accept a component as input and return a new component with enhanced functionality. HOCs allow you to encapsulate common logic or behaviors and apply them to multiple components.
Example:
import React from 'react';
const withLogger = (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
console.log('Component mounted:', WrappedComponent.name);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
const MyComponent = (props) => {
return <div>{props.message}</div>;
};
const EnhancedComponent = withLogger(MyComponent);
export default EnhancedComponent;
In this example, withLogger
is a HOC that logs when the wrapped component mounts. We enhance MyComponent
by wrapping it with withLogger
, creating EnhancedComponent
.
3. Explain the concept of context in React and when you would use it.
Context provides a way to pass data through the component tree without having to pass props manually at every level. It’s useful when data needs to be accessed by many components at different levels of nesting. Context is primarily used for global state management or configuration that needs to be accessible by multiple components.
Creating the Context:
// ThemeContext.js
import React from 'react';
const ThemeContext = React.createContext('light');
export default ThemeContext;
Providing the Context:
// App.js
import React from 'react';
import ThemeContext from './ThemeContext';
import Header from './Header';
import MainContent from './MainContent';
const App = () => {
return (
<ThemeContext.Provider value="dark">
<div>
<Header />
<MainContent />
</div>
</ThemeContext.Provider>
);
};
export default App;
Consuming the Context:
// Header.js
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
const Header = () => {
const theme = useContext(ThemeContext);
return (
<header style={{ background: theme === 'dark' ? '#333' : '#eee', color: theme === 'dark' ? '#fff' : '#333' }}>
<h1>My Website</h1>
</header>
);
};
export default Header;
// MainContent.js
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
const MainContent = () => {
const theme = useContext(ThemeContext);
return (
<div style={{ background: theme === 'dark' ? '#222' : '#fff', color: theme === 'dark' ? '#fff' : '#333' }}>
<p>This is the main content of the website.</p>
</div>
);
};
export default MainContent;
4. How do you optimize performance in React applications?
Performance optimization in React applications involves several techniques, including:
- Memoization: Use
React.memo
orReact.PureComponent
to prevent unnecessary re-renders of components. - Virtualization: Use libraries like
react-virtualized
for rendering large lists efficiently. - Code-splitting: Splitting your code into smaller chunks and loading them dynamically can improve initial load times.
- Using the production build: Ensure that you build your React application for production to benefit from optimizations like minification and dead code elimination.
5. What are controlled and uncontrolled components in React forms?
Controlled components are components whose form data is controlled by React. They have their state managed by React and update their state in response to user input through event handlers. In contrast, uncontrolled components store their form data in the DOM using refs. Their state is managed by the DOM itself, and React does not control their behavior.
Controlled Components:
import React, { useState } from 'react';
const ControlledForm = () => {
const [inputValue, setInputValue] = useState('');
const handleChange = (e) => {
setInputValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted value:', inputValue);
// You can submit the form data here or perform any other action
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={inputValue} onChange={handleChange} />
</label>
<button type="submit">Submit</button>
</form>
);
};
export default ControlledForm;
Uncontrolled Components:
import React, { useRef } from 'react';
const UncontrolledForm = () => {
const inputRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted value:', inputRef.current.value);
// You can submit the form data here or perform any other action
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" ref={inputRef} />
</label>
<button type="submit">Submit</button>
</form>
);
};
export default UncontrolledForm;
6. What is the purpose of the shouldComponentUpdate
method? When would you use it?
The shouldComponentUpdate
method is a lifecycle method in React that allows a component to control whether it should re-render or not. It is invoked before rendering when new props or state are being received. By default, shouldComponentUpdate
returns true
, indicating that the component should re-render. However, you can implement custom logic inside shouldComponentUpdate
to determine whether the update is necessary, based on the current props and state compared to the next props and state.
You would use shouldComponentUpdate
when you want to optimize performance by preventing unnecessary re-renders of a component. This is particularly useful when a component's rendering is computationally expensive or when its render output depends only on certain props or state changes.
Example:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Custom logic to determine whether the component should re-render
return this.props.data !== nextProps.data;
}
render() {
return <div>{this.props.data}</div>;
}
}
7. How do you handle routing in React applications?
Routing in React applications can be handled using either built-in solutions like React Router or custom routing implementations. React Router is the most popular library for routing in React applications. It provides a declarative way to define routes and navigate between different views within a single-page application (SPA).
Example:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Contact from './Contact';
const App = () => {
return (
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</Router>
);
};
export default App;
8. What are React hooks? Give examples of some commonly used hooks.
React hooks are functions that enable functional components to use state and other React features without needing to write a class. They were introduced in React 16.8 to simplify the development of React components and promote the use of functional components over class components.
Some commonly used React hooks include:
useState
: Allows functional components to manage state.useEffect
: Allows functional components to perform side effects (such as data fetching or DOM manipulation) after rendering.useContext
: Allows functional components to consume context values.useRef
: Allows functional components to access DOM elements or store mutable values persistently across renders.
9. Explain the differences between useState
and useReducer
hooks.
useState
:useState
is a React hook that allows functional components to manage state in a simple way. It returns a stateful value and a function to update that value. It is typically used for managing individual pieces of state.useReducer
:useReducer
is another React hook that is used for more complex state logic. It is similar to theuseState
hook, but it uses a reducer function instead of individual setters to manage state updates. It is typically used when the state logic is more complex or when multiple state values need to be updated together.
10. What is the purpose of the useEffect
hook in React?
The useEffect
hook is used in functional components to perform side effects after rendering. Side effects may include data fetching, DOM manipulation, or subscribing to external services. useEffect
runs after every render by default, including the initial render and subsequent updates.
useEffect
takes two arguments: a function that contains the side effect logic and an optional array of dependencies. The dependencies array allows you to specify values that the effect depends on. If any of the dependencies change between renders, the effect will re-run. If the dependencies array is empty, the effect only runs once after the initial render.
Example:
import React, { useState, useEffect } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// Update the document title after every render
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
};
11. Describe the concept of code splitting in React and its benefits.
Code splitting is a technique used in React applications to split the code into smaller chunks that can be loaded on demand. Instead of loading the entire application bundle upfront, code splitting allows you to load only the necessary code for the current route or feature, improving the initial loading time and overall performance of the application.
Benefits of code splitting include:
- Faster initial load time: By loading only the essential code needed for the initial view, code splitting reduces the time it takes for the application to become interactive.
- Better performance: Smaller code bundles result in faster download times and reduced memory consumption, leading to better performance, especially on low-bandwidth or mobile networks.
- Improved user experience: Users can start interacting with the application sooner, enhancing the overall user experience.
React provides several built-in mechanisms for code splitting, such as dynamic imports with import()
or React.lazy and Suspense for lazy loading components.
12. What is server-side rendering (SSR) in React? Why is it important?
Server-side rendering (SSR) is the process of rendering React components on the server and sending the fully rendered HTML to the client. Unlike traditional client-side rendering (CSR), where the HTML is generated on the client-side using JavaScript, SSR delivers pre-rendered content to the browser, improving the initial loading time and search engine optimization (SEO) of the application.
SSR is important for several reasons:
- Improved performance: SSR reduces the time it takes for the initial view to be displayed to the user, resulting in faster perceived performance.
- Better SEO: Search engines can crawl and index the pre-rendered HTML content, leading to better search engine rankings and discoverability.
- Accessibility: SSR ensures that the content is accessible to users with JavaScript disabled or using assistive technologies.
- First-contentful paint (FCP): SSR improves FCP metrics by delivering meaningful content to users faster, which positively impacts user engagement and retention.
Implementing SSR in React applications requires server-side infrastructure, such as Node.js with frameworks like Next.js or Express.js, to handle the rendering logic on the server.
13. How do you handle authentication and authorization in React applications?
Authentication and authorization in React applications are typically handled using techniques such as token-based authentication, session management, and role-based access control (RBAC). Here’s a high-level overview of the process:
- Authentication: Users authenticate themselves by providing credentials (e.g., username and password) to the server. Upon successful authentication, the server generates a token (e.g., JSON Web Token or JWT) and sends it back to the client. The client stores the token (usually in local storage or cookies) and includes it in subsequent requests to authenticate the user.
- Authorization: Once authenticated, the server validates the token and verifies the user’s identity and permissions. Authorization logic determines whether the user is allowed to access certain resources or perform specific actions based on their role or privileges. React components can conditionally render UI elements or make API requests based on the user’s authorization status.
Implementing authentication and authorization in React applications often involves integrating with backend authentication services, implementing client-side authentication logic, and securing API endpoints with access controls.
14. What is the purpose of the useMemo
hook in React?
The useMemo
hook is used in functional components to memoize expensive computations and optimize performance by avoiding unnecessary re-computations. It is similar to React.memo
for memoizing component renders but operates on arbitrary values rather than components.
The useMemo
hook takes a function and an array of dependencies as arguments. It memoizes the result of the function and re-computes it only when one of the dependencies changes. This allows you to optimize performance by caching the result of expensive computations and avoiding redundant calculations.
import React, { useState, useMemo } from 'react';
const FactorialCalculator = () => {
const [number, setNumber] = useState(0);
const factorial = useMemo(() => {
console.log('Calculating factorial...');
let result = 1;
for (let i = 1; i <= number; i++) {
result *= i;
}
return result;
}, [number]); // Memoize the result when the number changes
const handleChange = (e) => {
setNumber(parseInt(e.target.value));
};
return (
<div>
<label>
Enter a number:
<input type="number" value={number} onChange={handleChange} />
</label>
<p>Factorial of {number} is: {factorial}</p>
</div>
);
};
export default FactorialCalculator;
15. Explain the concept of error boundaries in React.
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of crashing the entire application. They provide a way to gracefully handle errors and prevent them from propagating up the component tree.
To create an error boundary in React, you define a component with a special componentDidCatch
lifecycle method that captures errors occurring in its child components. Inside componentDidCatch
, you can log the error, update component state, or render a fallback UI to inform the user about the error.
Error boundaries are particularly useful in large React applications with complex component hierarchies. They help isolate errors, maintain application stability, and provide a better user experience by gracefully handling unexpected errors.
Conclusion:
Mastering intermediate-level concepts in React is essential for building robust and efficient applications. In this article, we explored Flux architecture, Higher Order Components, context, performance optimization techniques, and controlled vs. uncontrolled components in React forms.
Stay tuned for our next article where we’ll tackle advanced-level ReactJS interview questions!
References: