React Flashcards
Also Next (33 cards)
Describe the pros and cons of using React.js
React is powerful for building UIs but needs good state management and setup!
Pros:
- Component-Based → Reusable, modular UI development.
- Virtual DOM → Faster updates & better performance.
- One-Way Data Binding → Predictable state management.
- Hooks & Functional Components → Cleaner and more efficient code.
- Large Ecosystem → Rich libraries, strong community support.
Cons:
- Steep Learning Curve → JSX, hooks, and state management require time to master.
- Boilerplate Code → Requires setup with Webpack, Babel, etc.
- Frequent Updates → Breaking changes and new APIs can require adjustments.
- SEO Challenges → Requires SSR (Next.js) for better search engine optimization.
How to Implement SSR in React
Server-Side Rendering (SSR) in React generates HTML on the server instead of the client for faster load times and better SEO.
Server Components reduce client-side JavaScript and improve performance.
Using Next.js (Best Approach)
- By default, components in app/ are Server Components.
- Fetch data directly inside components (no need for useEffect).
Server Components reduce client-side JavaScript and improve performance.
How to improving web page loading performance, and how to diagnose it
- How to Improve Performance:
Minimize & Optimize Assets
- Use image compression (WebP, AVIF).
- Minify CSS, JS, and HTML.
- Use lazy loading (loading=”lazy”) for images and iframes.
Optimize JavaScript
- Use code splitting (React.lazy(), dynamic imports).
- Defer non-critical JS (defer, async).
- Reduce unused JavaScript (tree shaking).
Improve Server & Network Efficiency
- Use a Content Delivery Network (CDN).
- Enable Gzip/Brotli compression.
- Implement Server-Side Rendering (SSR) or Static Generation (SSG).
- Use caching strategies (Cache-Control, ETags).
Reduce Render-Blocking Resources
- Optimize CSS & font loading (preload, font-display: swap).
- Minimize render-blocking scripts (async, defer).
- How to Diagnose Performance Issues
Google Lighthouse (Chrome DevTools)
- check Performance, Accessibility, Best Practices, SEO.
PageSpeed Insights (Google)
- Analyzes speed on mobile & desktop.
Web Vitals Metrics
- Largest Contentful Paint (LCP) → Measures loading speed.
- First Input Delay (FID) → Measures interactivity.
- Cumulative Layout Shift (CLS) → Measures visual stability.
Chrome Performance Tab
- Record a performance profile to detect slow scripts, excessive reflows, or blocking resources.
How does the virtual DOM work in React, and why is it important?
The virtual DOM (VDOM) in React is a lightweight copy of the actual DOM. When a component’s state changes, React updates the VDOM first, compares it to the previous version (diffing), and then updates only the changed parts in the real DOM (reconciliation).
Why it matters:
- Faster updates – Minimizes direct DOM manipulations.
- Efficient rendering – Updates only changed elements.
- Improved performance – Reduces reflows and repaints.
What are React Hooks, and how do they differ from class lifecycle methods?
React Hooks are functions that let you use state and lifecycle features in functional components.
Key Differences from Class Lifecycle Methods:
- Simpler & Cleaner – No need for class components.
- More Composable – Combine logic with custom hooks.
- Runs Per Render – Unlike lifecycle methods, hooks re-run with every render.
Examples:
- useState
– Manages state.
- useEffect
– Handles side effects (like componentDidMount, componentDidUpdate, componentWillUnmount).
Explain the concept of Higher-Order Components (HOCs) and provide use cases.
Higher-Order Components (HOCs) are functions that take a component and return an enhanced version of it. They allow reuse of component logic without modifying the original component.
Use Cases:
- Code Reusability – Share logic across multiple components.
- Authorization Handling – Wrap components to check user roles.
- Fetching Data – Attach API fetching logic to components.
Examples:
const withAuth = (Component) => (props) => { return isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />; };
Usage:
const ProtectedPage = withAuth(PageComponent);
What is Context API, and how does it help in managing global state?
Context API is a built-in React feature that allows global state management without prop drilling. It provides a way to share data (like themes, authentication, or settings) across components without passing props manually at every level.
How it Works:
1. Create a Context – const MyContext = React.createContext().
2. Provide the State – Wrap components with MyContext.Provider.
3. Consume the State – Use useContext(MyContext) in child components.
Why Use It?
- Avoids prop drilling – No need to pass props deep in the component tree.
- Simpler than Redux – Ideal for small to medium state management needs.
- Better Performance – Prevents unnecessary re-renders with proper optimization.
How does React’s reconciliation algorithm work?
React’s reconciliation algorithm determines the most efficient way to update the DOM when state or props change.
How It Works:
1. Virtual DOM Comparison – React creates a new virtual DOM and compares it with the previous one.
2. Diffing Algorithm – It finds changes using a key-based heuristic:
- Same type elements → Updates attributes.
- Different type elements → Replaces the old element.
- Lists with keys → Moves, adds, or removes items efficiently.
3. Batching & Commit Phase – Only necessary updates are applied to the real DOM.
Why It’s Efficient?
- Minimizes direct DOM updates, reducing performance costs.
- Uses keys in lists to optimize element reordering.
Describe the concept of “lifting state up” in React and provide an example.
In React, lifting state up means moving shared state to a common ancestor so multiple components can access and update it. This avoids duplicate state and ensures a single source of truth.
Example: Sibling Components Sharing State
function Parent() { const [count, setCount] = useState(0); return ( <div> <Counter count={count} /> <Button setCount={setCount} /> </div> ); } function Counter({ count }) { return <h1>Count: {count}</h1>; } function Button({ setCount }) { return <button onClick={() => setCount(prev => prev + 1)}>Increment</button>;
Why It’s Useful?
- Keeps state in sync across components.
- Prevents unnecessary duplication of state.
- Makes components more reusable and modular.
What is the purpose of the useReducer hook, and how does it compare to useState?
Purpose of useReducer
The useReducer hook manages complex state logic using a reducer function. It’s useful when state transitions depend on the previous state, making it a good alternative to useState for handling structured updates.
How It Compares to useState
- useState
is best for simple state updates where the next value is independent of the previous state.
- useReducer
is better for complex state logic, especially when multiple related state changes happen together.
- useReducer
uses an action-based approach, making updates more predictable and structured.
- It also helps avoid unnecessary re-renders by batching updates efficiently.
Example: Counter with useReducer
function reducer(state, action) { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; default: return state; } } function Counter() { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> </div> ); }
When to Use useReducer?
Use it when state logic is complex, involves multiple related updates, or depends on previous values (e.g., form handling, authentication, or global state management).
How can you optimize the performance of a React application?
Ways to Optimize React Performance
1. Use Memoization
- React.memo() prevents unnecessary re-renders of components.
- useMemo()
caches expensive calculations.
- useCallback()
memoizes functions to avoid re-creating them.
2. Lazy Load Components
- Use React.lazy()
and Suspense
to load components only when needed.
3. Optimize Re-renders
- Avoid unnecessary state updates.
- Use the dependency array in useEffect
wisely.
- Keep components as pure as possible.
4. Virtualize Long Lists
- Use libraries like react-window or react-virtualized to render only visible items in large lists.
5. Optimize React Keys
- Always use unique and stable keys in lists (avoid indexes as keys).
6. Debounce Expensive Operations
- Throttle or debounce events like search inputs to prevent excessive re-renders.
7. Minimize Bundle Size
- Remove unused dependencies.
- Use code splitting with import() for dynamic imports.
8. Use Efficient State Management
- Avoid unnecessary context re-renders.
- Use Redux, Recoil, or Zustand efficiently.
9. Optimize Images & Assets
- Use lazy loading for images.
- Compress images and use SVGs when possible.
10. Enable Server-Side Rendering (SSR) or Static Site Generation (SSG)
Use Next.js for better SEO and faster page loads.
Explain the role of keys in React lists and why they are important.
Role of Keys in React Lists
Keys in React help identify which items have changed, added, or removed in a list. They enable React’s reconciliation algorithm to efficiently update the DOM by minimizing unnecessary re-renders.
Why Are Keys Important?
- Optimized Rendering – Prevents unnecessary re-renders by allowing React to track items efficiently.
- Preserves Component State – Ensures React keeps track of component state when list order changes.
- Avoids UI Bugs – Without keys, React may mix up components, leading to unexpected behavior.
Best Practices for Keys
- Always use a unique and stable identifier (like an ID from a database).
- Avoid using array indexes as keys unless the list is static and never changes.
Example
const items = [{ id: 1, name: "Apple" }, { id: 2, name: "Banana" }]; return items.map((item) => <li key={item.id}>{item.name}</li>);
Incorrect (Using Index as Key)
return items.map((item, index) => <li key={index}>{item.name}</li>);
Using indexes as keys can cause incorrect reordering behavior when list items change.
What are React Portals, and when should they be used?
React Portals allow rendering components outside the normal React component tree, typically into a different DOM node. They help when you need a component to visually break out of its parent but still stay connected to React’s state and event system.
When to Use Portals?
- Modals & Dialogs – Prevents nesting issues and ensures proper z-index stacking.
- Tooltips & Popovers – Ensures they are not clipped by overflow: hidden or position: relative styles.
- Dropdown Menus – Avoids unwanted clipping inside parent containers.
Why Use Portals?
- Avoids CSS overflow issues in nested elements.
- Improves accessibility and UI behavior for floating elements.
- Maintains React state and event handling even when rendered outside the normal hierarchy.
How to Use Portals
1. Create a target DOM element in index.html
:
<div id="portal-root"></div>
- Render a component into the portal:
import ReactDOM from "react-dom"; function Modal({ children }) { return ReactDOM.createPortal( <div className="modal">{children}</div>, document.getElementById("portal-root") ); }
Describe the benefits and limitations of server-side rendering (SSR) with Next.js.
Benefits of Server-Side Rendering (SSR) in Next.js
1. Improved SEO – Pages are fully rendered on the server before reaching the browser, making it easier for search engines to index content.
2. Faster Initial Load – The browser receives a fully populated HTML page, reducing the time to First Contentful Paint (FCP).
3. Better Performance for Dynamic Content – Ideal for pages with frequently changing data, ensuring fresh content on every request.
4. Improved Social Media Sharing – Metadata (like Open Graph tags) is properly populated, enhancing link previews.
5. Direct Data Fetching – Fetches data before rendering, avoiding client-side API calls.
Limitations of SSR
1. Slower Time-to-First-Byte (TTFB) – Rendering on the server for every request adds latency compared to static pages.
2. Higher Server Load – Each request triggers a full re-render, increasing server resource consumption.
3. Complex Caching – Unlike Static Site Generation (SSG), SSR requires more caching strategies to improve performance.
4. Limited Client-Side Interactivity – While SSR delivers a fully rendered page, additional client-side hydration may be needed for interactive features.
5. More Expensive at Scale – Requires a powerful backend infrastructure, especially for high-traffic applications.
When to Use SSR?
- Pages that rely on real-time data (e.g., stock prices, news feeds).
- Authenticated pages where SEO matters.
- E-commerce product pages that need fresh inventory and price updates.
For other use cases, SSG (Static Site Generation) or ISR (Incremental Static Regeneration) may be more efficient.
How do you implement code splitting in a React application?
Code splitting helps optimize performance by loading only the necessary JavaScript for a given page or component, reducing initial load times.
- Using
React.lazy()
for Component-Level Splitting
The simplest way to split code in React is by using React.lazy()
with Suspense.
import React, { Suspense } from "react"; const LazyComponent = React.lazy(() => import("./HeavyComponent")); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> ); }
Best for: Loading individual components dynamically.
- Using Dynamic Imports for Route-Level Splitting
With React Router, you can lazy-load pages only when they are visited.
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import { lazy, Suspense } from "react"; const Home = lazy(() => import("./Home")); const About = lazy(() => import("./About")); function App() { return ( <Router> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> </Router> ); }
Best for: Splitting page-level components.
- Using Webpack’s Code Splitting (
import()
)
Webpack automatically splits code when you use dynamic import() in functions.
function loadComponent() { import("./HeavyComponent").then((module) => { const Component = module.default; // Use Component as needed }); }
Best for: Loading modules dynamically based on user actions.
- Leveraging Next.js Automatic Code Splitting
If using Next.js, it automatically code-splits at the page level. However, you can optimize it further:
import dynamic from "next/dynamic"; const DynamicComponent = dynamic(() => import("../components/HeavyComponent"), { ssr: false, loading: () => <p>Loading...</p>, });
Best for: Optimizing performance in Next.js apps.
Why Use Code Splitting?
- Improves initial page load time by reducing bundle size.
- Enhances performance on slow networks by loading only necessary components.
- Reduces JavaScript execution time by avoiding unnecessary scripts.
Tip: Always test performance improvements using Lighthouse or React DevTools Profiler after implementing code splitting.
Explain the concept of controlled and uncontrolled components in form handling.
Controlled Components
A controlled component is a form element whose value is controlled by React state. Every change updates the state, and the state defines the input’s value.
Why Use Controlled Components?
- Predictable – The state always reflects the input value.
- Easier Validation – You can validate user input in real-time.
- Better Control – Useful for dynamic form behaviors.
Example:
function ControlledInput() { const [value, setValue] = useState(""); return ( <input type="text" value={value} onChange={(e) => setValue(e.target.value)} /> ); }
Uncontrolled Components
An uncontrolled component manages its own state. Instead of storing the value in React state, you access it via refs when needed.
Why Use Uncontrolled Components?
- Simpler for Non-Dynamic Forms – Ideal for forms where you only need values on submit.
- Better Performance – React doesn’t re-render on every keystroke.
Example:
function UncontrolledInput() { const inputRef = useRef(); function handleSubmit() { alert(inputRef.current.value); } return ( <> <input type="text" ref={inputRef} /> <button onClick={handleSubmit}>Submit</button> </> ); }
Key Differences
- Controlled Components: State-driven, React updates value (useState
).
- Uncontrolled Components: DOM-driven, use refs (useRef
).
Which to Use?
- Use controlled components when you need validation, dynamic behavior, or real-time updates.
- Use uncontrolled components for simple forms where you only need values on submit.
What are custom hooks, and how can they help in reusing logic across components?
A custom hook is a reusable function that encapsulates stateful logic using React hooks (useState
, useEffect
, etc.). It helps avoid code duplication and improves code organization.
Why Use Custom Hooks?
- Reusability – Share logic across multiple components.
- Cleaner Code – Reduces clutter in components.
- Separation of Concerns – Keeps UI and logic independent.
When to Use Custom Hooks?
- Fetching data (useFetch
)
- Handling form state (useForm
)
- Managing local storage (useLocalStorage
)
- Debouncing input values (useDebounce
)
- Handling authentication (useAuth
)
How can you manage side effects in a React application?
Side effects in React refer to anything that affects something outside the component, such as fetching data, updating the DOM, or interacting with local storage. The best way to handle them is with the useEffect hook.
- Using useEffect for Side Effects
useEffect
runs after the component renders and can handle:
- Fetching data
- Event listeners
- Updating the DOM
- Subscriptions (e.g., WebSockets)
Example: Fetching Data
import { useEffect, useState } from "react"; function Users() { const [users, setUsers] = useState([]); useEffect(() => { fetch("https://jsonplaceholder.typicode.com/users") .then((res) => res.json()) .then((data) => setUsers(data)); }, []); // Empty dependency array -> Runs once when mounted return <ul>{users.map((user) => <li key={user.id}>{user.name}</li>)}</ul>; }
Why? The empty [] ensures the effect runs only on mount.
- Cleaning Up Side Effects
Some effects need cleanup, like event listeners or subscriptions, to prevent memory leaks.
Example: Cleanup on Unmount
useEffect(() => { fetch(`https://api.example.com/data?query=${searchTerm}`) .then((res) => res.json()) .then((data) => setResults(data)); }, [searchTerm]); // Runs every time `searchTerm` changes
Why? Only triggers the effect when searchTerm updates, avoiding unnecessary requests.
- Conditional Side Effects
You can make useEffect react to state/prop changes by including them in the dependency array.
Example: Fetching Data on State Change
useEffect(() => { fetch(`https://api.example.com/data?query=${searchTerm}`) .then((res) => res.json()) .then((data) => setResults(data)); }, [searchTerm]); // Runs every time `searchTerm` changes
Why? Only triggers the effect when searchTerm updates, avoiding unnecessary requests.
- Avoiding Unnecessary Re-renders
If the effect depends on an object/array, use memoization with useCallback or useMemo.
Example: Memoized Callback
const fetchData = useCallback(() => { fetch("/api/data").then((res) => res.json()).then(setData); }, []); // Memoized function
Why? Prevents function recreation on every render.
Discuss the trade-offs between using Redux and the Context API for state management.
Both Redux and** Context API** help manage global state in React, but they have different strengths and trade-offs.
When to Use Context API
- Best for small to medium-sized applications where state is not too complex.
- Simpler and built into React—no need for extra libraries.
- Easy to set up—just use React.createContext() and useContext().
Example Usage:
- Theme switching (light/dark mode).
- User authentication state (logged-in user info).
- Language preferences in a multi-language app.
Limitations:
- Re-renders all components that consume the context, which can lead to performance issues.
- Not ideal for deeply nested or frequently changing state.
When to Use Redux
- Best for large applications with complex and frequently changing state.
- Centralized store allows better state debugging and tracking with DevTools.
- Optimized re-renders—only the components that need updates will re-render.
- Middleware support (Redux Thunk, Redux Saga) for handling async logic.
Example Usage:
- E-commerce cart management (adding/removing items, persisting state).
- Large-scale dashboards with multiple state updates.
- Multiplayer game states where many components need access to real-time data.
Limitations:
- More boilerplate (reducers, actions, dispatches).
- Extra dependency—requires installing redux and react-redux.
- Learning curve—concepts like reducers and middleware may be overwhelming for beginners.
Which One Should You Choose?
- If your app only needs to share simple state (theme, auth, settings), use Context API.
- If your app has complex, deeply nested, or frequently changing state, use Redux.
For medium-sized apps, consider Zustand
or Recoil
—they offer simpler alternatives to Redux while improving performance over Context API.
What are fragments in React, and when should they be used?
React fragments (<></>
) allow you to group multiple elements without adding extra DOM nodes. This helps keep the HTML structure clean and avoids unnecessary wrappers like <div>
.
When to Use Fragments?
1. Avoid Unnecessary <div>
Wrappers
- Helps maintain cleaner and more semantic HTML.
- Prevents unwanted styles from affecting layout due to extra divs.
- When Returning Multiple Elements from a Component
- JSX requires a single root element, so fragments help return multiple elements without a wrapper div. - Better Performance in Lists
<React.Fragment key={}>
allows adding keys when rendering a list without an extra wrapper.
function ItemList({ items }) { return items.map(item => ( <React.Fragment key={item.id}> <h2>{item.name}</h2> <p>{item.description}</p> </React.Fragment> )); }
Why Use Fragments?
- Removes unnecessary DOM nodes
- Improves performance
- Prevents unwanted styles or layout shifts
- Useful in lists where a key is required
How does React handle events differently from vanilla JavaScript?
React event handling is different from vanilla JavaScript in a few key ways:
- Uses Synthetic Events
React wraps native DOM events in Synthetic Events, which standardizes event properties across browsers.
function handleClick(event) { console.log(event); // SyntheticEvent, not a native event } <button onClick={handleClick}>Click Me</button>
Why? Synthetic Events provide a consistent API across browsers, making event handling more predictable.
- Uses CamelCase for Event Names
Unlike vanilla JS (onclick), React uses camelCase (onClick).
React Syntax:
<button onClick={handleClick}>Click</button>
Vanilla JS Syntax:
document.getElementById("btn").onclick = handleClick;
- Event Listeners Are Attached to the Root (Event Delegation)
React attaches all event listeners to the root DOM instead of individual elements.
Why?
- Better Performance – Fewer event listeners, reducing memory usage.
- Works Well with Dynamic Elements – No need to rebind event handlers for newly added elements.
Vanilla JS (Event Attached to Each Button)
document.querySelectorAll("button").forEach(btn => btn.addEventListener("click", () => console.log("Clicked")) );
React (Single Listener at Root)
<button onClick={() => console.log("Clicked")}>Click Me</button>
- Prevents Default with event.preventDefault()
In vanilla JS, return false stops default behavior, but in React, you must use event.preventDefault() explicitly.
React Syntax:
function handleSubmit(event) { event.preventDefault(); console.log("Form submitted"); } <form onSubmit={handleSubmit}> <button type="submit">Submit</button> </form>
Vanilla JS Syntax:
document.getElementById("form").onsubmit = function(event) { event.preventDefault(); };
- Events Are Automatically Pooled (Optimized for Performance)
React pools events, meaning they are reused for efficiency. If you need event details asynchronously, you must store them manually.
Wrong (Event Gets Nullified)
<button onClick={(e) => setTimeout(() => console.log(e.type), 1000)}>Click</button>
Correct (Store Event First)
<button onClick={(e) => { const eventType = e.type; setTimeout(() => console.log(eventType), 1000); }}>Click</button>
Summary of Differences
- React uses Synthetic Events for cross-browser compatibility.
- Events use camelCase (onClick instead of onclick).
- React uses event delegation for better performance.
- Must use event.preventDefault()
instead of return false.
- Events are pooled for optimization.
Describe the use case and implementation of suspense and lazy loading in React.
Suspense and Lazy Loading optimize performance by loading components only when needed, reducing the initial JavaScript bundle size.
Use Cases:
- Improve initial load time by deferring loading of non-essential components.
- Load heavy components (e.g., dashboards, modals) only when required.
- Implement route-based code splitting for better performance.
Key Benefits of Suspense & Lazy Loading
- Improves performance by reducing the initial JS bundle.
- Enhances user experience with fallback loading states.
- Optimizes resource usage by loading only necessary components.
- Great for large apps with complex routes and heavy UI components.
- Component-Level Lazy Loading with React.lazy()
- Use
React.lazy()
to dynamically import components only when they are needed.
Example: Lazy Loading a Component
import React, { Suspense } from "react"; const LazyComponent = React.lazy(() => import("./HeavyComponent")); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> ); }
How It Works?
- React.lazy()
splits the component into a separate chunk.
- Suspense displays a fallback UI (Loading…) while the component loads asynchronously.
- Route-Based Lazy Loading with React Router
- Dynamically load pages only when a user visits a route.
Example: Lazy Loading Routes
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import { lazy, Suspense } from "react"; const Home = lazy(() => import("./Home")); const About = lazy(() => import("./About")); function App() { return ( <Router> <Suspense fallback={<p>Loading page...</p>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> </Router> ); }
Why Use This?
- Loads only the necessary JS chunks for the active route.
- Reduces initial page load time, improving performance.
- Suspense for Data Fetching (Experimental Feature)
- Use Suspense with React 18 and data-fetching libraries like React Query or Relay.
Example: Suspense with Data Fetching (React 18 & Suspense-enabled APIs)
import { Suspense } from "react"; function DataComponent() { const data = fetchData(); // Assume fetchData is a React resource return <p>{data.name}</p>; } function App() { return ( <Suspense fallback={<p>Loading data...</p>}> <DataComponent /> </Suspense> ); }
Why Use This?
- Better user experience – Avoids blank screens while fetching data.
- Improved loading state handling with centralized fallback UIs.
How can you use React.memo to optimize component rendering?
React.memo
is a higher-order component (HOC) that optimizes performance by preventing unnecessary re-renders. It memoizes the output of a component and only re-renders it when its props change.
- Basic Usage of React.memo
- Wrap a functional component with React.memo to avoid re-renders when props remain unchanged.
import React from "react"; const MemoizedComponent = React.memo(({ value }) => { console.log("Rendered!"); return <p>Value: {value}</p>; }); function App() { const [count, setCount] = React.useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> <MemoizedComponent value="Static Value" /> </div> ); }
What Happens?
- MemoizedComponent
won’t re-render when count changes because its value prop remains the same.
- This improves performance by avoiding unnecessary calculations inside the component.
- Using React.memo with Dynamic Props
By default, React.memo does a shallow comparison of props. If a prop is an object, array, or function, React may still trigger re-renders.
Example where memoization fails:
const MemoizedComponent = React.memo(({ obj }) => { console.log("Rendered!"); return <p>{obj.value}</p>; }); function App() { const [count, setCount] = React.useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> <MemoizedComponent obj={{ value: "Dynamic Object" }} /> </div> ); }
Issue: Since { value: “Dynamic Object” } is a new object on every render, React.memo doesn’t prevent re-rendering.
- Custom Comparison Function
If you need deeper prop comparisons, you can pass a custom arePropsEqual function to React.memo.
Example: Custom Comparison for Objects
const MemoizedComponent = React.memo( ({ obj }) => { console.log("Rendered!"); return <p>{obj.value}</p>; }, (prevProps, nextProps) => prevProps.obj.value === nextProps.obj.value );
Now the component will only re-render if obj.value actually changes.
- When NOT to Use React.memo
- Don’t use it for small or frequently updated components (e.g., text inputs).
- Avoid overusing it, as the memoization overhead may outweigh the performance benefits.
What are the common pitfalls of using useEffect, and how can they be avoided?
useEffect
is a powerful tool in React, but improper usage can lead to bugs, performance issues, and memory leaks. Here are common pitfalls and how to fix them:
- Missing Dependencies in the Dependency Array
Problem: If dependencies are missing, the effect may not update correctly when state or props change. - Infinite Loops Due to Incorrect Dependencies
Problem: Including a changing state inside useEffect without proper handling can cause an infinite loop. - Forgetting Cleanup for Subscriptions and Event Listeners
Problem: Not cleaning up effects can lead to memory leaks, especially when using event listeners or API subscriptions.
Example (Incorrect Usage)
useEffect(() => { window.addEventListener("resize", handleResize); }, []); // Event listener stays active even after unmounting
Fix: Return a cleanup function inside useEffect to remove listeners on unmount.
- Fetching Data Without Handling Component Unmount
Problem: Fetching data inuseEffect
without aborting can cause updates on an unmounted component.
Example: Use an AbortController to cancel the request if the component unmounts.
useEffect(() => { const controller = new AbortController(); fetch("https://api.example.com/data", { signal: controller.signal }) .then((res) => res.json()) .then(setData) .catch((err) => { if (err.name !== "AbortError") console.error(err); }); return () => controller.abort(); // Cleanup on unmount }, []);
- Running Effects on Every Render Without Optimization
Problem: A missing dependency array ([]) or passing unnecessary dependencies can cause effects to run on every render.
Example: If dependencies are dynamic, memoize them with useMemo or useCallback.
const memoizedValue = useMemo(() => computeExpensiveValue(data), [data]); useEffect(() => { console.log(memoizedValue); }, [memoizedValue]);
Something went wrong.
; } return this.props.children; } } function BuggyComponent() { throw new Error("I crashed!"); returnThis will never render
; } function App() { return (