HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogReact Performance Optimization: memo, useMemo, useCallback, and Profiler
Table of Contents▾
  • Rule #1: Profile Before You Optimize
  • Why Components Re-render
  • React.memo — Skip Re-renders When Props Haven't Changed
  • useMemo — Cache Expensive Calculations
  • useCallback — Stable Function References
  • The Memo/Callback Decision Guide
  • State Structure — The Root Cause of Re-renders
  • Context Performance
  • Virtualization for Long Lists
tutorials#react#performance#javascript

React Performance Optimization: memo, useMemo, useCallback, and Profiler

Fix React performance problems the right way — understand when components re-render, use React DevTools Profiler, and apply memo/useMemo/useCallback correctly.

Trong Ngo
February 25, 2026
5 min read

React performance optimization is one of the most misunderstood topics in frontend development. Most developers apply memo, useMemo, and useCallback everywhere — which actually makes things slower. This guide shows you how to optimize correctly.

Rule #1: Profile Before You Optimize

1. Open React DevTools → Profiler tab
2. Click "Record"
3. Interact with the slow part of your app
4. Click "Stop"
5. Look for:
   - Components with long render times (yellow/red flame graph)
   - Components re-rendering when they shouldn't (check "Why did this render?")
   - The same component rendering dozens of times

Don't guess — measure. Premature optimization wastes time and adds complexity.

Why Components Re-render

// A component re-renders when:
// 1. Its own state changes
// 2. Its parent re-renders (even if props didn't change)
// 3. A context it consumes changes
// 4. You call forceUpdate()

function Parent() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Click {count}</button>
      <Child name="Alice" />  {/* re-renders on EVERY parent render */}
    </div>
  )
}

function Child({ name }: { name: string }) {
  console.log("Child rendered")  // logs every time Parent renders
  return <p>Hello, {name}</p>
}

React.memo — Skip Re-renders When Props Haven't Changed

// Wrap with memo to skip re-render if props are the same
const Child = memo(function Child({ name }: { name: string }) {
  console.log("Child rendered")  // only logs when `name` changes
  return <p>Hello, {name}</p>
})

// ✗ memo won't help if you pass a new object/function each render
function Parent() {
  const [count, setCount] = useState(0)

  // ✗ New object created on every render — memo is useless
  return <Child style={{ color: "red" }} onClick={() => console.log("clicked")} />
}

// Custom comparison function
const ExpensiveList = memo(
  ({ items }) => <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>,
  (prev, next) => prev.items.length === next.items.length  // custom equal check
)

useMemo — Cache Expensive Calculations

// ✗ Without useMemo — recalculates on every render
function ProductList({ products, filter }) {
  const filtered = products
    .filter(p => p.category === filter)
    .sort((a, b) => b.price - a.price)  // expensive if products is large

  return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}

// ✓ With useMemo — only recalculates when products or filter changes
function ProductList({ products, filter }) {
  const filtered = useMemo(
    () => products
      .filter(p => p.category === filter)
      .sort((a, b) => b.price - a.price),
    [products, filter]  // dependency array
  )

  return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}

// ✗ DON'T useMemo for cheap operations — the overhead isn't worth it
const doubled = useMemo(() => count * 2, [count])  // overkill
const doubled = count * 2  // fine — just do this

useCallback — Stable Function References

// ✗ Without useCallback — new function reference on every render
// This breaks React.memo on Child because onClick is always "new"
function Parent() {
  const [count, setCount] = useState(0)

  const handleClick = () => {  // new function every render
    console.log("clicked")
  }

  return <MemoizedChild onClick={handleClick} />
}

// ✓ With useCallback — same function reference between renders
function Parent() {
  const [count, setCount] = useState(0)

  const handleClick = useCallback(() => {
    console.log("clicked", count)
  }, [count])  // only recreates when count changes

  return <MemoizedChild onClick={handleClick} />
}

// useCallback = useMemo for functions:
// useCallback(fn, deps) === useMemo(() => fn, deps)

The Memo/Callback Decision Guide

Should I use memo/useMemo/useCallback here?

  Is there a measured performance problem?
  ├── NO → Don't add it. Move on.
  └── YES
        │
        Is this a component re-rendering too often?
        ├── YES → React.memo (then ensure props are stable with useCallback/useMemo)
        └── NO
              │
              Is this an expensive calculation?
              ├── YES (>1ms, large data) → useMemo
              └── NO → Skip it

              Is this a function passed to a memoized child or useEffect dep?
              ├── YES → useCallback
              └── NO → Skip it

State Structure — The Root Cause of Re-renders

// ✗ One big state object — changing anything re-renders everything
const [state, setState] = useState({
  user: null,
  posts: [],
  comments: [],
  ui: { darkMode: false, sidebarOpen: true }
})

// ✓ Split state by update frequency
const [user, setUser] = useState(null)
const [posts, setPosts] = useState([])
const [darkMode, setDarkMode] = useState(false)

// ✗ Derived state stored in useState — causes double renders
const [items, setItems] = useState([])
const [count, setCount] = useState(0)  // should be derived!

// ✓ Derive it during render (or useMemo if expensive)
const [items, setItems] = useState([])
const count = items.length  // derived — no state needed

Context Performance

// ✗ All consumers re-render when any value in context changes
const AppContext = createContext({})

function AppProvider({ children }) {
  const [user, setUser] = useState(null)
  const [theme, setTheme] = useState("light")

  return (
    <AppContext.Provider value={{ user, setUser, theme, setTheme }}>
      {children}
    </AppContext.Provider>
  )
}

// ✓ Split contexts by update frequency
const UserContext = createContext(null)
const ThemeContext = createContext("light")

function Providers({ children }) {
  return (
    <UserContext.Provider value={user}>
      <ThemeContext.Provider value={theme}>
        {children}
      </ThemeContext.Provider>
    </UserContext.Provider>
  )
}
// Components that use ThemeContext won't re-render when user changes

Virtualization for Long Lists

// Rendering 10,000 DOM nodes is slow no matter what
// Virtualization renders only visible items

import { FixedSizeList } from "react-window"

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}     // container height
      width="100%"
      itemCount={items.length}
      itemSize={60}    // row height in px
    >
      {({ index, style }) => (
        <div style={style}>
          <ListItem item={items[index]} />
        </div>
      )}
    </FixedSizeList>
  )
}

// react-window renders only ~10-15 rows at a time regardless of list size

Try These Tools

JSON Formatter & Validator

Format, validate, and beautify JSON data instantly. Detect errors with precise line numbers.

Related Articles

PostgreSQL Performance Tuning: Indexes, Query Plans, and EXPLAIN ANALYZE

5 min read

Next.js App Router: The Complete Guide for 2025

5 min read

Web Performance Optimization: The 2025 Practical Guide

4 min read

Back to Blog

Table of Contents

  • Rule #1: Profile Before You Optimize
  • Why Components Re-render
  • React.memo — Skip Re-renders When Props Haven't Changed
  • useMemo — Cache Expensive Calculations
  • useCallback — Stable Function References
  • The Memo/Callback Decision Guide
  • State Structure — The Root Cause of Re-renders
  • Context Performance
  • Virtualization for Long Lists

Related Articles

PostgreSQL Performance Tuning: Indexes, Query Plans, and EXPLAIN ANALYZE

5 min read

Next.js App Router: The Complete Guide for 2025

5 min read

Web Performance Optimization: The 2025 Practical Guide

4 min read