Online Inter College
BlogArticlesCoursesSearch
Sign InGet Started

Stay in the loop

Weekly digests of the best articles — no spam, ever.

Online Inter College

Stories, ideas, and perspectives worth sharing. A modern blogging platform built for writers and readers.

Explore

  • All Posts
  • Search
  • Most Popular
  • Latest

Company

  • About
  • Contact
  • Sign In
  • Get Started

© 2026 Online Inter College. All rights reserved.

PrivacyTermsContact
Home/Blog/Technology
Technology

React Performance Optimization Techniques

CCodeWithGarry
April 1, 20248 min read877 views1 comments
React Performance Optimization Techniques

Building a React app that works is step one. Building one that flies is what separates good developers from great ones.

As your React application grows more components, more state, more data performance bottlenecks creep in silently. Pages feel sluggish. Users notice. Conversions drop.

🚀 A 100ms delay in load time can reduce conversions by up to 7%. React gives you powerful tools to prevent this but only if you know where to look.

This guide covers the most effective, battle-tested React performance techniques used by senior engineers at scale from quick wins to deep architectural improvements.


Technique 1 React.memo() : Stop Unnecessary Re-renders

Every time a parent component re-renders, all its children re-render too — even if their props haven't changed. React.memo() fixes this by memoizing the component output.

Without React.memo re-renders on every parent update:

const ProductCard = ({ name, price }) => {
  console.log('Re-rendering...'); // fires every time
  return <div>{name} — ${price}</div>;
};

With React.memo only re-renders when props change:

const ProductCard = React.memo(({ name, price }) => {
  return <div>{name} — ${price}</div>;
});

✅ Use React.memo when: A component renders often, receives the same props repeatedly, and is moderately expensive to render like list items, cards, or table rows.

❌ Skip React.memo when: The component is cheap to render, or its props change almost every render anyway memoization overhead outweighs the benefit.


Technique 2 useMemo() : Cache Expensive Calculations

If your component runs heavy computations on every render filtering large arrays, sorting data, running algorithms useMemo() caches the result and only recomputes when dependencies change.

Without useMemo recalculates every render:

const FilteredList = ({ products, query }) => {
  const results = products
    .filter(p => p.name.includes(query))
    .sort((a, b) => a.price - b.price);

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

With useMemo cached until products or query changes:

const FilteredList = ({ products, query }) => {
  const results = useMemo(() => {
    return products
      .filter(p => p.name.includes(query))
      .sort((a, b) => a.price - b.price);
  }, [products, query]);

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

✅ Use useMemo when: Filtering/sorting large arrays, complex mathematical calculations, deriving heavily transformed data from props or state.


Technique 3 useCallback() : Stabilize Function References

In JavaScript, every function is recreated on every render a new reference in memory each time. When you pass functions as props, child components see a "new" prop and re-render even when nothing logically changed. useCallback() solves this.

Without useCallback new function reference on every render:

const Parent = () => {
  const handleClick = () => {
    console.log('clicked');
  };
  return <Child onClick={handleClick} />;
  // Child re-renders every time Parent renders
};

With useCallback stable reference across renders:

const Parent = () => {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []); // empty deps = never recreated

  return <Child onClick={handleClick} />;
};

💡 useCallback is most powerful when combined with React.memo on the child component — together they form a complete shield against unnecessary re-renders.


Technique 4 Code Splitting with React.lazy()

Don't ship your entire app to users on first load. Code splitting breaks your bundle into smaller chunks that load only when needed dramatically improving initial load time.

Without code splitting everything loads upfront:

import Dashboard from './Dashboard';
import Analytics from './Analytics';
import Settings from './Settings';
// All 3 loaded even if user only visits Home

With React.lazy components load on demand:

import React, { Suspense, lazy } from 'react';

const Dashboard  = lazy(() => import('./Dashboard'));
const Analytics  = lazy(() => import('./Analytics'));
const Settings   = lazy(() => import('./Settings'));

const App = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <Routes>
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/analytics" element={<Analytics />} />
      <Route path="/settings"  element={<Settings />} />
    </Routes>
  </Suspense>
);

✅ Result: Users only download the code for pages they actually visit. A route-based split alone can reduce initial bundle size by 40–60%.


Technique 5 Virtualize Long Lists with React Window

Rendering 1,000+ items into the DOM is one of the most common React performance killers. Virtualization renders only the items currently visible on screen — the rest exist in memory only.

Install the library:

npm install react-window

Without virtualization all 10,000 rows in the DOM:

const HugeList = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={item.id}>{item.name}</li>
    ))}
  </ul>
);
// 10,000 DOM nodes = browser melts

With react-window only ~15 visible rows rendered:

import { FixedSizeList } from 'react-window';

const HugeList = ({ items }) => (
  <FixedSizeList
    height={600}
    itemCount={items.length}
    itemSize={48}
    width="100%"
  >
    {({ index, style }) => (
      <div style={style}>{items[index].name}</div>
    )}
  </FixedSizeList>
);

✅ Real-world impact: Rendering 10,000 rows drops from ~800ms to ~16ms. Your list becomes virtually free to render.


Technique 6 Avoid Anonymous Functions in JSX

This is a micro-optimization that adds up fast in large component trees. Anonymous functions defined inline in JSX create a new function reference on every render even without hooks.

❌ Bad new function reference every render:

<button onClick={() => handleDelete(item.id)}>
  Delete
</button>

✅ Better stable reference with useCallback:

const handleDelete = useCallback((id) => {
  deleteItem(id);
}, []);

<button onClick={() => handleDelete(item.id)}>
  Delete
</button>

✅ Best for list items, pass the ID through a data attribute:

const handleDelete = useCallback((e) => {
  const id = e.currentTarget.dataset.id;
  deleteItem(id);
}, []);

<button data-id={item.id} onClick={handleDelete}>
  Delete
</button>

Technique 7 State Colocation : Keep State Close to Where It's Used

One of the most underrated performance techniques requires zero libraries. Lifting state too high in the component tree causes unnecessary re-renders across huge swaths of your UI.

🔑 Golden rule: State should live as low as possible in the component tree as close as possible to the component that uses it.

❌ Problem global state causing wide re-renders:

const App = () => {
  const [searchQuery, setSearchQuery] = useState('');
  // Entire App re-renders on every keystroke
  return (
    <>
      <Header />
      <Sidebar />
      <SearchBar query={searchQuery} onChange={setSearchQuery} />
      <Results query={searchQuery} />
    </>
  );
};

✅ Solution colocated state, isolated re-renders:

const SearchSection = () => {
  const [searchQuery, setSearchQuery] = useState('');
  // Only SearchSection re-renders on keystroke
  return (
    <>
      <SearchBar query={searchQuery} onChange={setSearchQuery} />
      <Results query={searchQuery} />
    </>
  );
};

Technique 8 Image Optimization

Images are the #1 cause of slow page loads in most React apps. A few simple techniques eliminate most of the damage.

Use lazy loading for below-the-fold images:

<img
  src="/product-photo.jpg"
  alt="Product"
  loading="lazy"
  decoding="async"
/>

Use next-gen formats and responsive srcSet:

<picture>
  <source srcSet="/hero.webp" type="image/webp" />
  <source srcSet="/hero.avif" type="image/avif" />
  <img
    src="/hero.jpg"
    alt="Hero"
    width={1200}
    height={600}
    loading="eager"
  />
</picture>

✅ WebP images are 25–35% smaller than JPEG at the same quality. AVIF is even better — up to 50% smaller.


Performance Techniques Quick Reference

Technique

Problem Solved

Difficulty

Impact

React.memo()

Unnecessary child re-renders

Easy

High

useMemo()

Expensive recalculations

Easy

High

useCallback()

Unstable function references

Easy

Medium

Code Splitting

Large initial bundle

Medium

Very High

List Virtualization

Long list DOM overload

Medium

Very High

State Colocation

Over-broad re-renders

Medium

High

Avoid inline functions

Micro re-render overhead

Easy

Low–Medium

Image Optimization

Slow asset loading

Easy

Very High


Technique 9 Use the React DevTools Profiler

No optimization is complete without measuring. React DevTools has a built-in Profiler that shows exactly which components are rendering, how often, and how long each render takes.

How to use it:

  1. Install React Developer Tools browser extension

  2. Open DevTools → click the Profiler tab

  3. Click Record → interact with your app → click Stop

  4. Inspect the flame graph — wide orange bars = slow renders

  5. Look for components rendering more than expected

💡 Always profile before optimizing. Premature optimization based on guesses wastes time. The Profiler shows you exactly where your milliseconds are going.


Common Mistakes to Avoid

  • Wrapping every component in React.memo() — only memoize components with stable props that render frequently

  • Putting everything in global state — colocate state as low as possible

  • Optimizing before profiling — measure first, optimize second

  • Forgetting dependency arrays — wrong deps in useMemo/useCallback cause bugs or defeat the purpose

  • Virtualizing small lists — react-window adds complexity; only use it for 100+ items


The Optimization Priority Order

Follow this sequence for maximum impact with minimum effort:

  1. Profile first — identify the actual bottleneck with React DevTools

  2. Fix state architecture — colocate state, avoid unnecessary lifting

  3. Apply React.memo + useCallback — stop the re-render cascade

  4. Add useMemo — cache expensive computations

  5. Split your code — lazy load routes and heavy components

  6. Virtualize long lists — if rendering 100+ items

  7. Optimize assets — compress and lazy-load images

🎯 Remember: A fast app isn't built with one big optimization. It's built by consistently applying small, well-targeted improvements throughout your codebase.


Conclusion

React performance optimization isn't about tricks — it's about understanding how React works and writing code that works with the rendering model, not against it.

  • React.memo + useCallback → eliminate unnecessary re-renders

  • useMemo → cache expensive work

  • Code splitting + lazy loading → shrink your initial bundle

  • Virtualization → handle massive lists without melting the browser

  • State colocation → keep your component tree lean

  • Profiler → always measure before and after

Don't optimize everything. Optimize the right things — and your React app will feel fast, smooth, and production-ready at any scale.

Tags:#React#JavaScript#TypeScript#WebDevelopment#Programming#Frontend#WebPerformance#ReactHooks
Share:
C

CodeWithGarry

A passionate writer covering technology, design, and culture.

Related Posts

Zero-Downtime Deployments: The Complete Playbook
Technology

Zero-Downtime Deployments: The Complete Playbook

Blue-green, canary, rolling updates, feature flags — every technique explained with real failure stories, rollback strategies, and the database migration patterns that make or break them.

Girish Sharma· March 8, 2025
17m13.5K0

Comments (0)

Sign in to join the conversation

The Architecture of PostgreSQL: How Queries Actually Execute
Technology

The Architecture of PostgreSQL: How Queries Actually Execute

A journey through PostgreSQL internals: the planner, executor, buffer pool, WAL, and MVCC — understanding these makes every query you write more intentional.

Girish Sharma· March 1, 2025
4m9.9K0
Full-Stack Next.js Mastery — Part 3: Auth, Middleware & Edge Runtime
Technology

Full-Stack Next.js Mastery — Part 3: Auth, Middleware & Edge Runtime

NextAuth v5, protecting routes with Middleware, JWT vs session strategies, and pushing auth logic to the Edge for zero-latency protection — all production-proven patterns.

Girish Sharma· February 10, 2025
3m11.9K0

Newsletter

Get the latest articles delivered to your inbox. No spam, ever.