🟣 React interview questions and answers to help you prepare for your next technical interview in 2024.
React is an open-source, front-end JavaScript library for building user interfaces, that focuses on reusable components and virtual DOM for performance.
React's Virtual DOM is a lightweight in-memory representation of the actual DOM elements. When changes occur, React compares the current Virtual DOM with a shadow copy and efficiently updates only the changed portions in the real DOM.
This mechanism significantly reduces expensive direct DOM manipulations, resulting in improved performance and responsiveness in web applications.
React Components encapsulate both the visual and the corresponding logic. They can be either classes or pure functions. This modular architecture and the ability to nest and reuse components make React a powerful UI toolkit.
Components are broken down into two main types:
Key changes, starting from React 16.8:
useState
for state management and useEffect
for lifecycle management.Beyond this foundational structure, hooks offer extensive state, lifecycle, and context APIs, making functional components powerful building blocks.
React mandates a one-way data flow, empowering developers to understand and manage data propagation more effectively. This simplifies tracking, debugging, and validating data changes across the application.
While sibling components can communicate indirectly through shared parent components, direct communication among sibling components is typically discouraged.
JSX empowers developers by offering a more intuitive, HTML-like syntax for embedding JavaScript expressions. This marriage of UI and logic not only renders extensive possibilities but also promotes code organization and readability.
React enables a declarative style of programming: developers define the interface's desired state, and React ensures the DOM reflects that state. This approach is more intuitive and helps in designing clear, maintainable code.
React has been gaining momentum with an enthusiastic community regularly contributing new solutions, updates, and robust third-party libraries. The supportive ecosystem extends to comprehensive toolsets for better development and debugging (like React DevTools).
React's architecture is built on reusable components, fostering modular, consistent UI elements and logic that can be redeployed across projects or shared with others.
The Virtual DOM serves as a powerful performance amplifier, and features like providing keys to iterated lists ensure efficient and targeted DOM updates. React is also capable of server-side rendering, bolstering app speed and SEO-friendliness.
For application-wide state management, React provides Context API and libraries like Redux. Meanwhile, local state management with hooks like useState
streamlines state handling within components.
Here is the React code:
// Functional Component with useState hook
import React, { useState } from 'react';
export default function Button() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
// Class-based Component
import React, { Component } from 'react';
export default class Button extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Clicked {this.state.count} times
</button>
);
}
}
When comparing React, Angular, and Vue.js, a few key differentiators stand out.
setState
) and also external state management libraries like Redux, MobX, and the newer Context API.A React component represents a modular, reusable piece of the user interface. It can encapsulate both visual elements (rendered in the Virtual DOM) and application logic. React components come in two primary forms: function components and class components.
Function Components: These are stateless, simpler to read, and ideally used for small, specialized UI elements known as 'dumb' components. They are pure functions, perceptually faster because of fewer checks.
Class Components: These can maintain state and expose more advanced features like lifecycle methods. However, the introduction of hooks to function components in React 16.8 technically made state management possible without classes.
render()
React components generally use JSX (an XML-like syntax) to describe the UI and a render()
method to define the visual makeup.
render()
: Required for class components, it tells React what the component's output should be when rendered.Components in React link together, forming a tree structure. A root component is the entry point, and from there, it houses other components.
React follows a unidirectional data flow. This means data moves from the top of the component tree (parent) down to leaves (children) through component props. Changes are signaled back up the tree via callbacks.
Both function and class components can receive data via two main routes:
Class components support a series of lifecycle methods. These can be used to run code at specific points in the component's lifecycle, such as upon mounting (creation), updating, or unmounting (removal).
Custom classes and the lifecycle methods within were the primary mechanism for side effects earlier in React. While class-based components aren't as central to the framework with the advent of hooks, they're still relevant and in use, especially when using versions < 16.8.1 and realizing the components' lifecycle patterns in codebases.
Creating a React component involves defining its structure, behavior, and sometimes lifecycle methods for dynamic updates. Components can be functional or class-based.
Here's a Class-based component:
import React, { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
And here's a Functional one:
import React from 'react';
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;
Both examples showcase a basic greeting component that takes in a prop name
and displays a greeting message.
Many modern text editors and IDEs support JSX and JavaScript syntax, especially when integrated with linters like ESLint. This setup provides real-time feedback on errors and formatting issues.
It's common to see code bases following the Airbnb style guide, often coupled with Prettier for consistent and automated code formatting.
In the context of component creation, these standards can dictate whether to use single or double quotes for JSX attributes and the method for defining components.
JSX is a powerful JavaScript Extension that enables the seamless integration of HTML-like structures within React. Notably, it allows for a more intuitive component declaration and enhanced developer productivity.
Readable Syntax: Familiar HTML tags make parsing code and debugging simpler.
Component Embedding: JSX supports direct embedding of components, which enhances modularity.
Automatic Babel Conversion: Behind the scenes, JSX and its HTML-like tags are transpiled into JavaScript for browser compatibility.
Code Compactness: JSX helps avoid lengthy React.createElement
calls.
Type Safety: Modern IDEs provide extensive support for type checking and autocompletion with JSX.
Compile-Time Optimizations: JSX allows for compile-time optimizations, enhancing app performance.
Enable Optional Syntax Checks: For those developing in TypeScript, JSX enables Syntax Checks to ensure code quality.
Here is the JSX code
// JSX
const element = <h1>Hello, World!</h1>;
Here is the equivalent JS code transpiled by Babel:
// Transpiled JS
const element = React.createElement('h1', null, 'Hello, World!');
Concise Syntax: JSX provides a succinct, declarative approach to building UIs.
Improved Readability: Its obvious resemblance to HTML promotes code clarity and reduces cognitive load.
Static Type Checking: When used with TypeScript or Flow, JSX brings the benefits of type safety, reducing the probability of runtime errors.
Development Efficiency: By simplifying UI code and providing helpful developer features, JSX accelerates the development process.
React Ecosystem Integration: JSX is the preferred way to write components across the React ecosystem, fostering community best practices.
The Virtual DOM is a key concept in React, responsible for its high performance. It efficiently manages the DOM setup, minimizes updates, and then syncs them to the actual DOM tree.
Initial Rendering: When the application starts, React creates a simplified in-memory representation of the DOM, called the Virtual DOM.
Tree Comparison: During each state change, React builds a new Virtual DOM representation. It then compares this updated representation against the previous one to identify what has changed. This process is often called "reconciliation".
Selective Rendering: React determines the most minimal set of changes needed to keep the Virtual DOM in sync with the actual DOM. This approach, known as "reconciliation", is a performance booster as it reduces unnecessary updates.
Batched Updates: React performs the actual DOM updates in a batch, typically during the next animation frame or when no more updates are being made. This batching leads to optimized DOM operations, further enhancing performance.
One-Way Sync: After the in-memory Virtual DOM and the actual DOM have been reconciled and the necessary updates identified, React syncs these changes in a one-way process, from the Virtual DOM to the actual DOM. This approach helps prevent unnecessary visual glitches and performance hits.
Asynchronous Handling: React schedules state changes, ensuring performance by bundling multiple changes that can be processed together. This aids in avoiding unnecessary Virtual DOM updates and ensures efficient tree comparisons.
Preventing Direct DOM Manipulation: React applications typically avoid manual DOM manipulation. Instead, all changes are made through React, which then uses its Virtual DOM mechanism to batch and apply these changes to the actual DOM.
Support for Cross-Platform Environments: The Virtual DOM gives sturdy cross-platform capabilities, enabling consistent and optimized performance irrespective of the underlying operating system or hardware.
React's Virtual DOM is primarily powered through its component architecture and extensive use of JavaScript, fundamentally changing how web applications are built and perform. Its virtuous efficiency is a testament to React's prowess as a leading front-end framework and contributes to the seamless user experiences React applications are known for providing.
Let's look at the various aspects and differences between Class Components and Functional Components.
Class Components:
class
keyword for component definition.Functional Components:
Hooks
in React 16.8 for state and lifecycle control.Class Components:
render()
method.Functional Components:
useState()
and useEffect()
for state and lifecycle management respectively.Class Components:
Functional Components:
Class Components:
this.state
and this.setState()
to manage state.Functional Components:
useState
hook to enable state management in functions.Class Components:
componentDidMount
and componentWillUnmount
.Functional Components:
useEffect()
to handle actions based on state and props changes.Class Components:
Functional Components:
useContext
, have become proficient in handling shared state.Class Components:
this
context.Functional Components:
React simplifies the process of managing and handling events through its use of synthetic events.
React abstracts browser events into what are known as synthetic events. This ensures a consistent interface across different browsers.
When handling events, React behaves consistently across all elements, not just form elements.
React events use camelCase, unlike HTML, which is helpful for both consistency and avoiding reserved words in JavaScript.
Use boolean attributes in JSX for default browser events.
React provides special interfaces for certain types of events: input components benefit from the value
attribute, while media components make use of src
or other similar attributes specific to their type.
Here is the JavaScript code:
import React from 'react';
class Form extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
In React, props and state are both used to propagate and manage data. However, they have different roles and management patterns.
Props (short for "properties") are used to pass data from a parent component to a child one. Once passed, props in the child component are read-only and can't be directly modified by the child.
State is used to manage data within a component, and is mutable. Any changes to state values trigger a component re-render.
When a component receives new props, React will merge them with any existing state. However, it won't override state values unless you explicitly set them.
Since React re-renders the entire component when you update state, it's important to be efficient in state management. Tools like useMemo
or shouldComponentUpdate
can help optimize re-renders.
The useState
hook (along with other hooks like useEffect
) allows functional components to manage state, bringing them closer in capability to class components.
Prior to the introduction of hooks in React 16.8, state was the exclusive domain of class components. But now, both state and its associated lifecycle hooks belong to functional components as well.
Here is the JavaScript code:
import React, { useState } from 'react';
// Button Component
const Button = ({ text, color }) => {
return <button style={{background: color}}>{text}</button>;
};
// ColorPicker Component
const ColorPicker = () => {
const [color, setColor] = useState('blue');
const changeColor = (newColor) => {
setColor(newColor);
};
return (
<div>
<Button text="Red" color="red" onClick={() => changeColor('red')} />
<Button text="Blue" color="blue" onClick={() => changeColor('blue')} />
<Button text="Green" color="green" onClick={() => changeColor('green')} />
</div>
);
};
// App Component
const App = () => {
return <ColorPicker />;
};
Data propagation in React components primarily relies on two mechanisms:
Props: For unidirectional data flow, parent components pass data to their children via props.
Callback Functions: Data moves up the tree when children invoke specific functions passed down from their parents.
Let's have a look at the best-practices for these two mechanisms.
Role: Primarily used for one-way data flow. The parent furnishes the child with props that the child component can neither alter nor reassign.
Best Practices:
Code Example: Read-Only Checkbox:
Your task is to write the full code for the React Application to demonstrate passing data to child components using props.
In this code example, App
maintains the optionSelected
state that it shares with the DropDown
and SelectedOption
components. DropDown
uses the optionSelected
state to determine which option was picked, shared with SelectedOption
to display it.
// src/components/DropDown.tsx
interface DropDownProps {
options: string[]
}
const DropDown: React.FC<DropDownProps> = ({ options }) => {
const [selected, setSelected] = React.useState(0);
return (
<div>
<div>Options:</div>
{options.map((opt, index) => (
<button key={index} onClick={() => setSelected(index)}>{opt}</button>
)}
<SelectedOption option={options[selected]} />
</div>
);
};
// src/components/SelectedOption.tsx
interface SelectedOptionProps {
option: string
}
const SelectedOption: React.FC<SelectedOptionProps> = ({ option }) => {
return <div>You selected: {option}</div>;
};
// src/App.tsx
const App: React.FC = () => {
const options = ['Apple', 'Banana', 'Cherry'];
return <DropDown options={options} />;
};
export default App;
Stateful components in React are fueled by internal states, allowing them to adapt to user interactions and data changes.
By invoking this.setState()
, components update their state, triggering a re-render and ensuring the UI and state are in sync.
Dynamic Interactions: For components that require dynamic updates, such as a counter that increments on every click.
User Input Handling: Useful for capturing and validating user inputs in forms.
Data Fetching: To manage and display data obtained from API calls.
Here is the JavaScript code:
import React, { Component } from 'react';
class ClickCounter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleIncrement = () => {
this.setState(prevState => ({ count: prevState.count + 1 }));
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleIncrement}>Increment</button>
</div>
);
}
}
export default ClickCounter;
useState
is a built-in React Hook that empowers components to preserve stateful values. It amalgamates a stateful value with a state-modifying function, enabling direct manipulation.
Hooks are utility functions that enable you to manage state, side effects, and other React features in function components.
useState
Stateful Value: The first element in the tuple returned by useState
carries the current state, like any other state in React.
Setter Function: The second element is a function that determines the state's new value. Upon invocation, it imparts this new state to the component, just as setState
does in classes.
Given value
as the stateful value and setValue
as the setter function, calling setValue(newValue)
will alter value
to newValue
.
useState
Lazy Initialization: If the stateful value necessitates a computationally intensive or time-consuming setup, employing useState
ensures that this setup occurs exclusively when the component is first rendered rather than on every update.
Referential Integrity: If you employ the useState
Hook at distinct spots within a component or even dissimilar components, React guarantees that each endeavor manages its unique state underlying value, akin to using this.state
in classes.
Here is the React Component:
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
React encourages unidirectional data flow, primarily passing data from parent to child. However, occasional need arises to update parent state from a child component. This can be facilitated using specific patterns and techniques.
Props Callback: Pass a function onStateChange
as a prop which the child can call to update parent state.
Context API: Use Context
to make state accessible and modifiable from descendant components.
UseRef and ForwardRef: Utilize useRef
and forwardRef
to get a reference to a child component, allowing you to directly manipulate its properties.
Global State Management: Implement a global state management solution like Redux or MobX if state changes are pervasive.
Data Services: Use a service to manage shared state, which can be updated and read by different components.
Here is the React Component:
// App.js - Parent component
import React, { useState } from 'react';
import Child from './Child';
function Parent() {
const [state, setState] = useState('');
const updateState = (newState) => {
setState(newState);
};
return <Child updateParentState={updateState} />;
}
// Child.js - Child component
import React from 'react';
function Child({ updateParentState }) {
const handleClick = () => {
updateParentState('New state from child!');
};
return <button onClick={handleClick}>Update Parent</button>;
}
In this example, the Parent
component maintains the state, which is updated via the function updateState
passed as a prop to Child
. When a button inside Child
is clicked, the updateParentState
function updates the parent state.
Lifting State Up in React entails managing state in parent components to propagate it to multiple children, typically to ensure synchronization or data flow.
React components communicate using props
, where child components receive data from parents. During lifting state up, the parent maintains the state and passes down relevant data as props.
Here is the React code:
class RectangleAreaCalculator extends React.Component {
constructor(props) {
super(props);
this.state = { width: 0, height: 0 };
}
render() {
return (
<div>
<ShapeInput
type="number"
label="Width"
value={this.state.width}
onChange={(e) => this.setState({ width: e.target.value })}
/>
<ShapeInput
type="number"
label="Height"
value={this.state.height}
onChange={(e) => this.setState({ height: e.target.value })}
/>
<ShapeArea area={this.state.width * this.state.height} />
</div>
);
}
}
const ShapeInput = ({ type, label, value, onChange }) => (
<div>
<label>{label}</label>
<input type={type} value={value} onChange={onChange} />
</div>
);
const ShapeArea = ({ area }) => <div>Area: {area}</div>;
In this example, the RectangleAreaCalculator
maintains the width
and height
state and passes them as props to the ShapeInput
components. The ShapeArea
component calculates the area and receives width
and height
as props, keeping its state logic-free.
For small-scale apps or in situations with data that lacks a clear source of "truth", the technique might introduce unnecessary complexity.
Aiming for a balance between centralized and localized state management is key, and React provides tools like useContext
and useState
that cater to both requirements.
Redux and the Context API serve as tools for managing state in large React applications. Here are situations which might call for one or the other, or even both:
Full App Coverage: Redux operates on a global state, enabling consistent app behavior and simplifying state transitions across components.
Predictable State Changes: Changes in Redux follow a strict flow, optimizing teamwork and troubleshooting.
Time Travel: Redux devtools allow for easy time travel, aiding in bug detection and state history visualization.
Performance Optimization: With its selective rendering feature, connect
from react-redux
ensures only the required components are updated, thus mitigating wasteful renders.
Optimal for Bigger Apps:
Large Apps with Complex State: Ideal for apps with intensive state requirements and a multitude of components.
Frequent Inter-Component Communications: When different sections of your application need to exchange data often, a central store, such as Redux, can streamline this process.
Simplicity: The Context API is built into React and is more straightforward to set up, making it a more convenient choice for simpler state needs.
Single Point of Configuration: Context API allows for a centralized point of configuration for state, similar to Redux.
Easier to Understand for Smaller Apps: It's less intensive and thus, is easier to explain and understand, especially for junior developers or in smaller teams.
When No Nested Components: Great for smaller applications or ones with minimal nesting of components, eliminating the need to prop-drill or create additional HOCs or render-props to share state.
For App-Wide Configurations: It's useful for handling global configurations, such as themes or user authentication.
Newer React Projects Involving Hooks: Since the Context API underwent significant improvements with the introduction of Hooks, it's an appealing choice for new projects.