React JS is a JavaScript library for building user interfaces, primarily used for creating single-page applications where data can change without reloading the page.
We need React to efficiently build dynamic and interactive user interfaces with reusable components, which improves development speed and maintainability.
ReactJS works by using a component-based architecture to build UIs, where each component manages its own state and behavior. React uses a virtual DOM to efficiently update the actual DOM by only rendering the components that change. When the state or props of a component change, React compares the virtual DOM with the previous one, finds the differences, and updates the DOM efficiently. This approach ensures better performance and a smooth user experience.
useState
and useEffect
, allow you to manage state and side effects in functional components, simplifying the development process.
The Virtual DOM is a lightweight, in-memory representation of the real DOM in React. When a component’s state or props change, React updates the Virtual DOM instead of the real DOM immediately. It then compares the updated Virtual DOM with a snapshot of the previous one (a process called “diffing”) and identifies the minimum number of changes required. React then updates only the parts of the real DOM that have changed, improving performance by minimizing direct DOM manipulations.
JSX is a syntax extension for JavaScript that allows you to write HTML-like code within JavaScript, making it easier to visualize the UI structure.
Browsers do not natively understand JSX, as it is not a standard JavaScript syntax. JSX is a syntax extension for JavaScript that looks similar to HTML but is meant for React. Here's how browsers handle it:
JSX Transpilation: JSX is first transpiled (translated) into regular JavaScript using tools like Babel. During this process, JSX elements are converted into React.createElement()
calls.
React.createElement(): Once transpiled, JSX is represented as JavaScript objects describing the DOM tree. These objects are then processed by React, which renders them into actual DOM elements.
For example, JSX like:
const element = <h1>Hello, world!</h1>;
is transpiled to:
const element = React.createElement('h1', null, 'Hello, world!');
In React, components are the building blocks used to create user interfaces. They encapsulate parts of the UI and allow you to split the UI into independent, reusable pieces.
1. Functional Components:
Functional components are the simplest form of components in React. They are JavaScript functions that return JSX (React’s syntax to write HTML-like code).
Example:
function MyComponent() {
return (
<div>
<h1>Hello, React!</h1>
</div>
);
}
export default MyComponent;
2. Class Components:
Class components are ES6 classes that extend React.Component
. They can also hold and manage state and lifecycle methods.
Example:
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return (
<div>
<h1>Hello, React!</h1>
</div>
);
}
}
export default MyComponent;
Lifecycle methods in class components are hooks that allow you to execute code at specific points during a component's lifecycle, including:
useState
and useEffect
.
Data is passed from parent to child components using props.
<ChildComponent name="Luis" />
, where name
is a prop.setState
or useState
.const [count, setCount] = useState(0)
.Key Differences:
children
prop in React?The children
prop in React is a special prop that allows components to pass other elements or components as its content. It is used to display whatever is placed between the opening and closing tags of a component.
defaultProps
in React?defaultProps
in React is a way to define default values for props in a component. If a prop is not provided by the parent component, the component will use the value specified in defaultProps
.
Prop drilling is basically a situation when the same data is being sent at almost every level due to requirements in the final level. The problem with Prop Drilling is that whenever data from the Parent component will be needed, it would have to come from each level, Regardless of the fact that it is not needed there and simply needed in last.
Pure Components in React optimize performance by implementing a shallow comparison on props and state, re-rendering only when there are changes, thus preventing unnecessary renders.
You can create elements in a loop in React by using JavaScript’s array methods, such as map()
.
Keys are very important in lists for the following reasons:
Keys are unique identifiers used in lists to help React identify which items have changed, been added, or removed. They are important for efficient re-rendering and to maintain the correct order of elements.
One-way data binding in React means that data flows in a single direction, from parent components to child components through props, ensuring predictable and controlled updates to the UI.
Hooks in React are special functions that allow developers to use state and other React features (such as lifecycle methods)
useState
hook in React?The most used hook in React is the useState()
hook. It allows functional components to manipulate DOM elements before each render. Using this hook we can declare a state variable inside a function but only one state variable can be declared using a single useState()
hook. Whenever the useState()
hook is used, the value of the state variable is changed and the new variable is stored in a new cell in the stack.
useEffect()
hook and how to manage side effects?The useEffect()
hook in React eliminates the side effect of using class based components. It is used as an alternative to componentDidUpdate()
method. The useEffect()
hook accepts two arguments where second argument is optional.
A custom hook is a function that encapsulates reusable logic using React hooks. It always starts with “use” to follow the convention.
Conditional Rendering: In React, it means rendering different UI elements based on certain conditions, often using JavaScript operators like `if`, `ternary`, or `&&
A Fragment is a wrapper used in React to group multiple elements without adding extra nodes to the DOM.
CSS modules are a way to locally scope the content of your CSS file. We can create a CSS module file by naming our CSS file as App.modules.css and then it can be imported inside App.js file using the special syntax mentioned below.
Syntax:
import styles from './App.module.css';
react-redux
?Redux is a predictable state container for JavaScript applications. It helps you write apps that behave consistently, run in different environments (client, server, and native), and are easy to test. Redux manages an application’s state with a single global object called Store.
react-redux
?Redux solves the state transfer problem by storing all of the states in a single place called a store. So, managing and transferring states becomes easier as all the states are stored in the same convenient store. Every component in the application can then directly access the required state from that store.
react-redux
.The state of your whole application is stored in an object tree within a single-store.
A single state tree makes it easier to debug or inspect an application
It gives you a faster development cycle by enabling you to persist in your app's navigation state
The only way to change the state is to initiate an action, an object describing what happened.
This feature ensures that no events like network callbacks or views can change the state. They can only express an intent to change the state
Actions are just plain objects; they can be logged, serialized, stored, and later replayed for debugging or testing purposes
To specify how actions transform the state tree, you need to write pure reducers.
The user can return new state objects instead of mutating the previous stateThe user can start with a single reducer, and, as the app grows, can split it off into smaller reducers that manage specific parts of the state treeBecause reducers are just functions, it’s easy to control the order in which they are called, pass additional data, or even make reusable reducers
Context is a React feature that allows you to share state and other values between components without having to pass props manually at every level.
A provider is used to provide context to the whole application whereas a consumer consume the context provided by nearest provider. In other words The Provider acts as a parent it passes the state to its children whereas the Consumer uses the state that has been passed.
Handling buttons in React is a straightforward process. Typically, buttons are used to trigger events, perform actions, or update state when clicked.
import React, { useState } from 'react';
function ButtonExample() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // Update the state when the button is clicked
};
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click me</button> {/* onClick event */}
</div>
);
}
export default ButtonExample;
function ButtonExample() {
const handleClick = (name) => {
console.log(Hello, ${name});
};
return (
<button onClick={() => handleClick('Luis')}>Say Hello</button>
);
}
Preventing Default Behavior
If a button has default behavior (like a form submission), you can prevent it using event.preventDefault().
function ButtonExample() {
const handleSubmit = (event) => {
event.preventDefault(); // Prevent form submission
console.log('Form was submitted');
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
4. Handling Multiple Buttons If you have multiple buttons, you can use the id or other identifying attributes to differentiate their actions.
function MultipleButtons() {
const handleButtonClick = (action) => {
console.log(${action} button clicked);
};
return (
<div>
<button onClick={() => handleButtonClick('Save')}>Save</button>
<button onClick={() => handleButtonClick('Delete')}>Delete</button>
</div>
);
}
In React, inputs are handled using controlled components, where the input's value is tied to the component's state through the value
attribute, and updated using the onChange
event. This allows for efficient management of inputs like text fields, checkboxes, and dropdowns.
const [inputValue, setInputValue] = useState('');
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
React Router is a library that enables dynamic routing in React applications, allowing you to navigate between different components or pages without reloading the entire application.
React Router is a popular library for handling routing in React applications, allowing you to manage navigation between different components or pages. The main components of React Router include:
1. BrowserRouter
import { BrowserRouter, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Route path="/" component={Home} />
</BrowserRouter>
);
}
2. Routes
and Route
Routes
is a wrapper that allows multiple routes to be defined.Route
defines a path and the component that should render when the URL matches that path.import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
3. Link
<a>
) tags to ensure React Router can manage the navigation.import { Link } from 'react-router-dom';
function Navigation() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
);
}
4. useNavigate
import { useNavigate } from 'react-router-dom';
function Form() {
const navigate = useNavigate();
const handleSubmit = () => {
// After form submission, navigate to another page
navigate('/success');
};
return <button onClick={handleSubmit}>Submit</button>;
}
5. useParams
import { useParams } from 'react-router-dom';
function Product() {
const { id } = useParams(); // Extract 'id' from URL
return <div>Product ID: {id}</div>;
}
6. useLocation
import { useLocation } from 'react-router-dom';
function CurrentLocation() {
const location = useLocation();
return <div>Current Path: {location.pathname}</div>;
}
7. Navigate
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated }) {
return isAuthenticated ? <Dashboard /> : <Navigate to="/login" />;
}
8. Outlet
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<header>Header</header>
<Outlet /> {/* Renders child routes */}
<footer>Footer</footer>
</div>
);
}
Routing in React is typically managed by libraries like React Router. It works by mapping URL paths to specific components. When a user navigates to a URL, the router displays the corresponding component without reloading the page, creating a seamless single-page application experience.
Data fetching in React can be implemented using various methods, depending on your needs and the complexity of the application. The most common approach is to use React’s lifecycle methods or hooks like useEffect
to fetch data from APIs or other sources.
1. Using useEffect
Hook
The useEffect
hook allows you to perform side effects in functional components, including data fetching. This is the most common approach when working with functional components in React.
import React, { useState, useEffect } from 'react';
function DataFetchingComponent() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Fetch data when the component mounts
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => {
setData(data);
setLoading(false); // Data is fetched, stop loading
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []); // Empty dependency array ensures it only runs once when component mounts
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default DataFetchingComponent;
2. Using axios
for Data Fetching
Instead of using the native fetch
API, you can use third-party libraries like Axios, which makes handling promises simpler and supports features like request cancellation.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function AxiosDataFetching() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Fetch data using Axios
axios.get('https://api.example.com/data')
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default AxiosDataFetching;
3. Using async/await
for Cleaner Syntax
You can use async/await
inside the useEffect
hook for more readable asynchronous code.
import React, { useState, useEffect } from 'react';
function AsyncDataFetching() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Error fetching data');
}
const data = await response.json();
setData(data);
setLoading(false);
} catch (error) {
setError(error.message);
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default AsyncDataFetching;
React is a JavaScript library focused on building user interfaces with a component-based architecture and uses a virtual DOM for efficient updates. It’s flexible and allows you to choose additional libraries for routing and state management.
Angular is a full-fledged JavaScript framework that provides a complete solution for building web applications. It uses TypeScript, follows a model-view-controller (MVC) or model-view-view model (MVVM) architecture, and includes built-in tools for routing, state management, and dependency injection.
Button.js
import React from 'react';
import PropTypes from 'prop-types';
import './Button.css'; // Optional: for styling
const Button = ({ onClick, type, children, className, disabled }) => {
return (
<button
onClick={onClick}
type={type}
className={`btn ${className}`}
disabled={disabled}
>
{children}
</button>
);
};
Button.propTypes = {
onClick: PropTypes.func,
type: PropTypes.oneOf(['button', 'submit', 'reset']),
children: PropTypes.node.isRequired,
className: PropTypes.string,
disabled: PropTypes.bool,
};
Button.defaultProps = {
onClick: () => {},
type: 'button',
className: '',
disabled: false,
};
export default Button;
Button.css
.btn {
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #ddd;
}
.btn:disabled {
cursor: not-allowed;
opacity: 0.5;
}
Usage Example:
import React from 'react';
import Button from './Button';
function App() {
const handleClick = () => {
alert('Button clicked!');
};
return (
<div>
<Button onClick={handleClick} className="primary-button">
Click Me
</Button>
<Button onClick={handleClick} type="submit" disabled>
Submit
</Button>
</div>
);
}
export default App;
Form.js
import React, { useState } from 'react';
const Form = () => {
const [formData, setFormData] = useState({
email: '',
password: '',
username: ''
});
const [errors, setErrors] = useState({
email: '',
password: '',
username: ''
});
const [isSubmitted, setIsSubmitted] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
const validate = () => {
const { email, password, username } = formData;
const newErrors = {};
if (!email) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(email)) {
newErrors.email = 'Email address is invalid';
}
if (!password) {
newErrors.password = 'Password is required';
} else if (password.length < 6) {
newErrors.password = 'Password must be at least 6 characters long';
}
if (!username) {
newErrors.username = 'Username is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
setIsSubmitted(true);
// Handle form submission (e.g., API call)
console.log('Form data submitted:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <p className="error">{errors.email}</p>}
</div>
<div>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <p className="error">{errors.username}</p>}
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <p className="error">{errors.password}</p>}
</div>
<button type="submit">Submit</button>
{isSubmitted && <p className="success">Form submitted successfully!</p>}
</form>
);
};
export default Form;
Form.css
.error {
color: red;
font-size: 0.875em;
}
.success {
color: green;
font-size: 1em;
}
form {
max-width: 400px;
margin: auto;
}
div {
margin-bottom: 15px;
}
Usage:
import React from 'react';
import Form from './Form';
import './Form.css';
function App() {
return (
<div className="App">
<h1>Registration Form</h1>
<Form />
</div>
);
}
export default App;
Controlled components in React have their form data managed by the component’s state, with updates handled via onChange
events. Uncontrolled components manage their form data through the DOM, using refs to access values. Controlled components offer better control and synchronization, while uncontrolled components are simpler but less flexible.
A React Component can go through four stages of its life as follows.
Initialization: This is the stage where the component is constructed with the given Props and default state. This is done in the constructor of a Component Class.
Mounting: Mounting is the stage of rendering the JSX returned by the render method itself.
Updating: Updating is the stage when the state of a component is updated and the application is repainted.
Unmounting: As the name suggests Unmounting is the final step of the component lifecycle where the component is removed from the page.
useState
and useRef
in React?useState
: Creates state variables that trigger re-renders when updated.
useRef
: Creates a mutable reference that persists across renders without causing re-renders, commonly used to access DOM elements directly.
StrictMode
in React?StrictMode
is a tool for highlighting potential problems in a React application. It activates additional checks and warnings for its descendants, such as identifying unsafe lifecycle methods, detecting side effects in components, and ensuring best practices. StrictMode
is a development-only feature and has no impact on production builds.
Modularizing code in a React project involves organizing components, utilities, and styles into separate files and directories, making the codebase more maintainable and scalable. This can be achieved by:
Custom hooks in React are functions that start with use
and allow you to encapsulate and reuse stateful logic across multiple components. They can call other hooks and return values such as state variables or functions. For example, a custom hook like useCounter
can manage counter state and provide methods to increment, decrement, or reset the counter.
React implements reusability in components through several key features:
A Higher-Order Component (HOC) in React is a function that takes a component and returns a new component with additional props or behavior. It is used to enhance or reuse component logic across different components without modifying the original component. For example, an HOC can add loading functionality to a component by wrapping it and conditionally rendering a loading indicator.
Code splitting in React is a technique to improve performance by breaking up the application into smaller chunks that are loaded on demand. This is achieved using React.lazy
and Suspense
, which dynamically import components and show a fallback UI while they are being loaded.
Redux is a state management library for JavaScript applications, often used with React. It provides a centralized store for all application state, making it easier to manage and debug complex states.
Redux works by following a unidirectional data flow:
Redux Toolkit is a library that simplifies using Redux by providing tools like createSlice
for reducing boilerplate code, configureStore
for setting up the store, and built-in support for handling asynchronous logic. It streamlines state management in Redux and improves the development experience.
useCallback
hook in React?The useCallback
hook is used to memoize a function, preventing it from being recreated on every render unless its dependencies change. This is useful for optimizing performance, especially when passing functions to child components.
useMemo
hook in React?The useMemo
hook in React is used to memoize expensive calculations and prevent unnecessary re-computation. It recalculates the result only when its dependencies change, improving performance by avoiding re-renders of components with heavy computations.
React.memo
and how is it used for performance optimization?React.memo
is a higher-order component (HOC) in React that optimizes performance by preventing unnecessary re-renders of a component. It works by memoizing the rendered output of a functional component and only re-renders it when its props change.
In React's Context API, the Provider supplies data to components, while the Consumer accesses that data. The Provider
wraps components and passes a value
prop, and the Consumer
retrieves that value. In functional components, the useContext
hook is often used instead of Consumer
for easier access to context.
Reconciliation is the process React uses to update the DOM by comparing the new and previous virtual DOM trees and applying the minimal set of changes needed to the real DOM.
TypeScript improves React code safety and scalability by adding static type checking, which helps catch errors during development rather than at runtime. It ensures that components receive the correct props, enforces consistent data structures, and improves code maintainability. TypeScript also enhances scalability by making refactoring easier and improving code readability through type definitions.
Testing ensures that your React application works as expected. It helps catch bugs early, ensures that individual components function correctly, and maintains application stability as the codebase grows. Testing also provides confidence when refactoring or adding new features, as it can quickly identify unintended side effects.
If a React application is rendering slowly, you can:
React.memo()
or pureComponent
: Prevent unnecessary re-renders of components that haven't changed.lazy
and Suspense
.useCallback
and useMemo
: Optimize expensive calculations and function references in functional components.
lazy
function and the Suspense
component.To implement lazy loading in React, use the React.lazy
function to dynamically import components and the Suspense
component to handle the loading state while the component is being loaded.
Use React.lazy
to load the component dynamically:
const LazyComponent = React.lazy(() => import('./LazyComponent'));
Wrap the lazy-loaded component with Suspense
, providing a fallback UI (e.g., a loader) while the component is being fetched:
import React, { Suspense } from 'react';
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
export default App;
This ensures that LazyComponent
is only loaded when needed, optimizing performance.
1. Create a function to test:
// sum.js
export function sum(a, b) {
return a + b;
}
2. Write the test:
// sum.test.js
import { sum } from './sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
3. Run the test:
In your terminal, run:
npm run test
import React, { useState, useEffect } from 'react';
const Timer = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// Set up an interval
const intervalId = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
// Cleanup function to clear the interval on component unmount
return () => {
clearInterval(intervalId);
};
}, []); // Empty dependency array means the effect runs once on mount
return (
<div>
<h1>Timer: {count}</h1>
</div>
);
};
export default Timer;
import React, { useState, useCallback } from 'react';
// Debounce function
const debounce = (func, delay) => {
let timeout;
return (...args) => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func(...args);
}, delay);
};
};
// Throttle function
const throttle = (func, delay) => {
let lastCall = 0;
return (...args) => {
const now = new Date().getTime();
if (now - lastCall < delay) return;
lastCall = now;
func(...args);
};
};
const DebounceThrottleInputs = () => {
const [debouncedValue, setDebouncedValue] = useState('');
const [throttledValue, setThrottledValue] = useState('');
// Handle debounced input
const handleDebouncedInput = useCallback(
debounce((value) => {
setDebouncedValue(value);
}, 1000),
[]
);
// Handle throttled input
const handleThrottledInput = useCallback(
throttle((value) => {
setThrottledValue(value);
}, 1000),
[]
);
return (
<div>
<div>
<label>Debounced Input (1s delay): </label>
<input
type="text"
onChange={(e) => handleDebouncedInput(e.target.value)}
/>
<p>Debounced Value: {debouncedValue}</p>
</div>
<div>
<label>Throttled Input (1s delay): </label>
<input
type="text"
onChange={(e) => handleThrottledInput(e.target.value)}
/>
<p>Throttled Value: {throttledValue}</p>
</div>
</div>
);
};
export default DebounceThrottleInputs;
How it Works:
Debounce: The debounce
function delays invoking the input handler until after a set delay (1 second here) has passed since the last event.
Throttle: The throttle
function limits how often the input handler can be invoked (at most once per second).
Two Inputs:
To optimize the performance of a React application, you can follow these strategies:
React.memo()
: Wrap functional components with React.memo
to prevent unnecessary re-renders if the props don’t change.useMemo
and useCallback
: Use useMemo
to memoize expensive calculations and useCallback
to memoize functions, preventing them from being recreated on every render.React.lazy
and Suspense
: Use lazy loading to split your code and load components only when needed, reducing initial load time.shouldComponentUpdate
or PureComponent
for class components.react-window
) for large lists to render only visible items in the DOM.NODE_ENV=production
) to take advantage of React’s optimizations.
Reconciliation in React is the process by which React updates the DOM efficiently. It compares the new virtual DOM (after state or props changes) with the previous virtual DOM to determine the minimal set of changes needed to update the real DOM.
List virtualization in React is a performance optimization technique used to efficiently render large lists or grids of elements by only rendering visible items in the viewport. Instead of rendering the entire list at once, virtualization ensures that only a small subset of items are rendered at a time, reducing the number of DOM nodes and improving performance.
useMemo
, and useCallback
, and when is it appropriate to use each?React.memo
: Prevents re-renders of a component if its props haven’t changed.useMemo
: Memoizes a value to avoid recalculating it unless its dependencies change.useCallback
: Memoizes a function to prevent its recreation on every render unless its dependencies change.Use React.memo
for components, useMemo
for expensive calculations, and useCallback
for stable functions passed to child components.
Action: An object that describes what happened in the application, containing a type
and optional payload
.
Store: A centralized place to hold the application state, managed by reducers.
Reducer: A pure function that takes the current state and an action, then returns a new state based on the action type.
Server-Side Rendering (SSR) in React refers to the process of rendering React components on the server and sending fully rendered HTML to the browser. This contrasts with traditional client-side rendering, where React components are rendered on the client after the JavaScript bundle is downloaded.
How SSR Works:
On the server, React components are rendered into HTML.
This HTML is sent to the client.
The browser displays the HTML content immediately (providing faster initial load).
React then "hydrates" the app by attaching event handlers and making it interactive.
Use Cases:
Improved SEO: SSR is beneficial for SEO because search engines can index the fully rendered content, as opposed to waiting for JavaScript to load.
Faster Initial Page Load: Since the browser gets fully rendered HTML, users can see the content faster, especially on slower networks or devices.
Better Performance for Large Apps: SSR can help in rendering large applications quickly by offloading the initial rendering to the server.
Here are some common anti-patterns in React development to avoid:
1. Mutating State Directly
setState
or state updater functions.// Bad
this.state.value = newValue;
// Good
this.setState({ value: newValue });
2. Overusing State
// Bad
this.setState({ derivedValue: this.state.value * 2 });
// Good
const derivedValue = value * 2;
3. Unnecessary Re-renders
React.memo
for functional components and shouldComponentUpdate
for class components.// Bad
const MyComponent = ({ onClick }) => <button onClick={onClick}>Click</button>;
// Good
const MyComponent = React.memo(({ onClick }) => <button onClick={onClick}>Click</button>);
4. Ignoring Key Prop in Lists
key
prop for elements in lists.// Bad
{items.map(item => <div>{item}</div>)}
// Good
{items.map(item => <div key={item.id}>{item}</div>)}
5. Complex Component Logic
// Bad
const ComplexComponent = () => { /* large logic */ };
// Good
const PartOne = () => { /* logic */ };
const PartTwo = () => { /* logic */ };
const ComplexComponent = () => <><PartOne /><PartTwo /></>;
6. Using Inline Functions in Render
useCallback
.// Bad
<button onClick={() => doSomething()}>Click</button>;
// Good
const handleClick = () => doSomething();
<button onClick={handleClick}>Click</button>;
7. Not Using Error Boundaries
class ErrorBoundary extends React.Component {
// Implement error handling logic
}
8. Over-fetching Data
Web Workers are a feature of web browsers that allow JavaScript to run in the background, on a separate thread from the main execution thread. This enables you to perform computationally intensive tasks without blocking the user interface, leading to a smoother user experience.
Benefits of Using Web Workers
useLayoutEffect
and useEffect
. When would you prefer one over the other?useEffect
:
useEffect
will execute after the browser has painted the UI.
useLayoutEffect
:
Example:
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
function Example() {
const [height, setHeight] = useState(0);
const divRef = useRef(null);
// useLayoutEffect: Measure DOM before painting
useLayoutEffect(() => {
const measuredHeight = divRef.current.getBoundingClientRect().height;
setHeight(measuredHeight);
}, []);
// useEffect: Fetch data after render
useEffect(() => {
// Fetch some data here
console.log("Data fetched after render");
}, []);
return (
<div ref={divRef} style={{ height: '100px' }}>
The div height is: {height}
</div>
);
}
export default Example;
In this example:
useLayoutEffect
ensures that the height of the div
is measured before the browser paints the UI.useEffect
handles side effects that don’t need to block rendering, like fetching data.
To improve user experience in a React application by preloading components or data, you can apply several strategies such as lazy loading, code splitting, and prefetching data or components. These techniques help ensure that resources or data are available ahead of time, reducing wait times and enhancing the overall performance.
1. Preloading Components:
Preloading or pre-rendering components that the user is likely to interact with can prevent delays when those components are needed.
Using React.lazy()
and Suspense
:
Normally, React.lazy loads components on demand. You can combine lazy loading with preloading to ensure components are loaded ahead of time (e.g., when hovering over a button).
const LazyComponent = React.lazy(() => import('./MyComponent'));
// Preload the component when hovering over a button
function preloadComponent() {
import('./MyComponent');
}
function App() {
return (
<div>
<button onMouseEnter={preloadComponent}>
Preload Component
</button>
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
</div>
);
}
2. Preloading Routes:
In large apps, you may use code splitting to load routes only when needed, but preloading them for smoother transitions can help avoid delays.
React Router with Route Preloading:
Combine React.lazy()
with dynamic imports to preload route components that are likely to be visited soon.
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
// Preload the 'About' component
function preloadAbout() {
import('./About');
}
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about" onMouseEnter={preloadAbout}>
About (Preload)
</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
</Suspense>
</Router>
);
}
3. Preloading Data:
You can preload data in advance by fetching data before the user navigates to the relevant section, avoiding waiting times for data-heavy operations.
Using React Query
for Prefetching:
Libraries like React Query
allow you to prefetch data before it’s needed, improving UX by ensuring data is already available when the component is rendered.
import { useQuery, QueryClient } from 'react-query';
const queryClient = new QueryClient();
// Fetch data function
const fetchUserData = async () => {
const res = await fetch('/api/user');
return res.json();
};
// Component that uses data
function UserComponent() {
const { data, isLoading } = useQuery('user', fetchUserData);
if (isLoading) return <div>Loading...</div>;
return <div>User: {data.name}</div>;
}
// Preload data
queryClient.prefetchQuery('user', fetchUserData);
4. Prefetching Assets (Images, Fonts, etc.):
Preloading images, fonts, or other assets ensures smoother transitions when components are displayed. You can use <link rel="preload">
in the HTML or JavaScript APIs to preload assets.
function preloadImage(src) {
const img = new Image();
img.src = src;
}
function ImageComponent() {
const imageUrl = 'https://example.com/image.jpg';
preloadImage(imageUrl); // Preload the image
return <img src={imageUrl} alt="example" />;
}
5. Prefetching with rel="prefetch"
:
Modern browsers support prefetching of resources (JS, CSS, etc.). You can add rel="prefetch"
links to preload components that the user might need later.
<link rel="prefetch" href="/static/js/component.js" />
Summary of Preloading Strategies:
React.lazy
with prefetching (via import()
) to load components in advance.onMouseEnter
events and dynamic imports.React Query
to prefetch data before it is needed in a component.<link rel="prefetch">
.
To optimize the rendering of a component that frequently receives new props or state updates:
React.memo
: Wrap the component with React.memo
to prevent unnecessary re-renders if the props haven’t changed.useCallback
and useMemo
: Memoize functions and computed values to avoid recalculations or re-creating objects on every render.react-window
) to only render visible items in large lists.
Common performance bottlenecks in large React applications and ways to address them include:
React.memo
to prevent unnecessary re-renders of components when props haven’t changed. Optimize component structures and reduce frequent state updates.React.lazy
and Suspense
) to load parts of the app only when needed.react-window
) to render only the visible portion of large datasets.useCallback
to memoize callback functions to avoid creating new functions on every render.<link rel="preload">
for critical assets.
Managing deeply nested or complex global state in React using the Context API while minimizing unnecessary re-renders can be achieved through several strategies:
useState
or event handlers) to group multiple state updates into a single render cycle.Example:
import React, { createContext, useContext, useState, useMemo } from 'react';
// Create context
const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
const [state, setState] = useState({
user: null,
theme: 'light',
// other state...
});
const value = useMemo(() => ({
user: state.user,
theme: state.theme,
setUser: (user) => setState((prev) => ({ ...prev, user })),
setTheme: (theme) => setState((prev) => ({ ...prev, theme })),
}), [state.user, state.theme]); // Only update when these change
return (
<GlobalContext.Provider value={value}>
{children}
</GlobalContext.Provider>
);
};
// Usage in a component
const UserProfile = () => {
const { user, setUser } = useContext(GlobalContext);
return (
<div>
<h1>{user ? user.name : 'Guest'}</h1>
<button onClick={() => setUser({ name: 'John Doe' })}>Login</button>
</div>
);
};
In React, error boundaries are used to catch JavaScript errors in components and provide a fallback UI instead of crashing the entire application. You can implement error boundaries by creating a class component that implements the componentDidCatch
lifecycle method and the getDerivedStateFromError
static method.
Implementation of an Error Boundary:
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render shows the fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log the error to an error reporting service
console.error("Error logged:", error, errorInfo);
}
render() {
if (this.state.hasError) {
// Fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Usage:
Wrap components that might throw errors with the ErrorBoundary
:
function App() {
return (
<ErrorBoundary>
<SomeComponent />
</ErrorBoundary>
);
}
Example: E-Commerce Checkout Process: In a large e-commerce application, the checkout process involves multiple components: cart summary, payment processing, and order confirmation. If a user encounters an error during payment processing (e.g., a network issue or a validation error), an error boundary can catch this error without crashing the entire checkout flow.
Without error boundaries, a JavaScript error could prevent the user from completing their purchase, leading to a poor user experience and potential loss of sales. By using error boundaries, you can display a friendly error message, allow the user to retry the payment, or redirect them back to the cart, ensuring the application remains stable and functional.
Debugging performance issues in a React application involves several steps and tools to identify and resolve bottlenecks.
1. Identify Performance Issues:
2. Use React DevTools:
3. Chrome DevTools:
4. Lighthouse:
5. Analyze Network Requests:
6. Code Analysis:
React.memo
, useMemo
, and useCallback
).
7. Monitor with External Tools:
8. Profiling and Optimization:
useEffect
or useState
with Jest and React Testing Library?To test components that use hooks like useEffect
or useState
with Jest and React Testing Library, you need to focus on testing how the component behaves when the state changes or side effects are triggered, rather than testing the implementation details of the hooks themselves. The idea is to ensure that your component works as expected with the hooks.
1. Testing useState
:
For components that use useState
, the goal is to test how the component's state changes based on user interactions or other events.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
Test for useState
with Jest and React Testing Library:
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments count when button is clicked', () => {
render(<Counter />);
const button = screen.getByText(/Increment/i);
const countText = screen.getByText(/Count: 0/i);
// Ensure the initial state is rendered
expect(countText).toBeInTheDocument();
// Simulate a button click
fireEvent.click(button);
// Expect the count to increment
expect(screen.getByText(/Count: 1/i)).toBeInTheDocument();
});
In this example:
We render the Counter
component.
Use fireEvent.click()
to simulate a user interaction that triggers a state change.
Check that the UI updates as expected after the state change.
2. Testing useEffect
:
For components using useEffect
, you can test how the component behaves after the effect is triggered (e.g., fetching data, subscribing to events).
import React, { useEffect, useState } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
}
fetchData();
}, []);
if (!data) {
return <div>Loading...</div>;
}
return <div>Data: {data.name}</div>;
}
export default DataFetcher;
Test for useEffect
with Jest and React Testing Library:
To test asynchronous operations like fetching data, you’ll use mocking with Jest. You can mock the fetch
API and control its return values to simulate different scenarios (like success or failure).
import { render, screen, waitFor } from '@testing-library/react';
import DataFetcher from './DataFetcher';
// Mock the fetch API
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: 'Test Data' }),
})
);
test('renders fetched data after loading', async () => {
render(<DataFetcher />);
// Initially, "Loading..." text should appear
expect(screen.getByText(/Loading/i)).toBeInTheDocument();
// Wait for the data to load
await waitFor(() => screen.getByText(/Data: Test Data/i));
// Check if the data is rendered
expect(screen.getByText(/Data: Test Data/i)).toBeInTheDocument();
});
In this example:
fetch
: We mock the fetch
function to return test data without making real network requests.waitFor
: Since fetching data is asynchronous, we use waitFor
to wait until the component updates and displays the fetched data.
3. Testing Custom Hooks:
If you have a custom hook, it’s a good practice to extract the logic from the component and write unit tests specifically for the hook.
You can use @testing-library/react-hooks
for testing custom hooks.
import { useState, useEffect } from 'react';
function useFetchData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const result = await response.json();
setData(result);
setLoading(false);
}
fetchData();
}, [url]);
return { data, loading };
}
export default useFetchData;
Test for the custom hook:
import { renderHook } from '@testing-library/react-hooks';
import useFetchData from './useFetchData';
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: 'Test Data' }),
})
);
test('should fetch and return data', async () => {
const { result, waitForNextUpdate } = renderHook(() => useFetchData('/api/data'));
// Initially, loading should be true
expect(result.current.loading).toBe(true);
// Wait for the hook to finish the data fetching
await waitForNextUpdate();
// After the update, data should be present and loading should be false
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual({ name: 'Test Data' });
});
In this example:
renderHook
.waitForNextUpdate
is used to wait for the asynchronous fetch operation to complete before asserting the data.
Summary of Steps to Test Components with Hooks:
useState
: Simulate user interactions that trigger state changes, and check that the component’s output updates correctly.useEffect
: Mock external dependencies (e.g., fetch
) and verify the component behavior once the effect has completed.waitFor
or similar methods to handle async updates in the UI.renderHook
to test the logic of hooks independently from component.Description:
Create a custom hook called useForm
to manage complex forms with validation and state management. The hook should:
Support real-time field validation.
Handle state for multiple fields (e.g., Name, Email, Password).
Display error messages when validation fails.
Provide a way to reset the form to its initial state.
Requirements:
The form should include fields: Name, Email, and Password.
The hook should accept a validation object that contains the validation rules for each field.
Display error messages under each field if it does not meet the validation rules.
Include a button to reset the form.
Solution:
import { useState } from 'react';
// Custom hook for form management
function useForm(initialState, validate) {
const [values, setValues] = useState(initialState);
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
if (validate[name]) {
const error = validate[name](value);
setErrors({
...errors,
[name]: error,
});
}
};
const handleReset = () => {
setValues(initialState);
setErrors({});
};
return {
values,
errors,
handleChange,
handleReset,
};
}
// Validation rules
const validate = {
name: (value) => (value ? '' : 'Name is required'),
email: (value) =>
/\S+@\S+\.\S+/.test(value) ? '' : 'Invalid email address',
password: (value) =>
value.length >= 6 ? '' : 'Password must be at least 6 characters',
};
// Form Component using useForm
function Form() {
const initialState = { name: '', email: '', password: '' };
const { values, errors, handleChange, handleReset } = useForm(
initialState,
validate
);
return (
<form>
<div>
<label>Name:</label>
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
/>
{errors.name && <span>{errors.name}</span>}
</div>
<div>
<label>Email:</label>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
/>
{errors.email && <span>{errors.email}</span>}
</div>
<div>
<label>Password:</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
/>
{errors.password && <span>{errors.password}</span>}
</div>
<button type="button" onClick={handleReset}>
Reset Form
</button>
</form>
);
}
export default Form;
Description: You have a list of 10,000 items that need to be rendered on a page. Implement a component that optimizes the rendering of this list using "list virtualization" techniques. The component should load only the visible items in the viewport and render additional elements as the user scrolls.
Requirements:
react-window
or implement a custom solution.
Solution using react-window
:
First, install the library:
npm install react-window
Then, implement the component:
import React from 'react';
import { FixedSizeList as List } from 'react-window';
// Sample data: Array of 10,000 items
const items = Array.from({ length: 10000 }, (_, index) => `Item ${index + 1}`);
function Row({ index, style }) {
return (
<div style={style} className="list-item">
{items[index]}
</div>
);
}
function VirtualizedList() {
return (
<List
height={400} // Height of the viewport
itemCount={items.length} // Total number of items
itemSize={35} // Height of each item
width={300} // Width of the list
>
{Row}
</List>
);
}
export default VirtualizedList;
useForm
hook to manage form state and validation. It accepts an initial state and validation rules, handling updates and form resets.react-window
:
We used the react-window
library to handle list virtualization for a large list of 10,000 items.
Only the items visible in the viewport are rendered, improving performance significantly by reducing the number of DOM elements.