DEV Community

Cover image for 5 React JS Tricks to Improve Site Speed and Efficiency in 2026
Aamir Jamal
Aamir Jamal

Posted on

5 React JS Tricks to Improve Site Speed and Efficiency in 2026

A practical guide for frontend developers who want faster, leaner React apps - without the fluff.


Performance is no longer a "nice to have." With Core Web Vitals directly influencing SEO rankings and user retention dropping sharply after a 2-second load time, a slow React app is a business problem. The good news? React's ecosystem in 2026 gives us sharper tools than ever.

Here are five tricks that actually move the needle.


1. Stop Over-Rendering with useMemo, useCallback, and memo - But Actually Use Them Right

React developers often reach for useMemo and useCallback as a reflex, which ironically hurts performance due to the overhead of memoization on cheap computations. The trick in 2026 is being surgical about it.

The rule of thumb: memoize when the computation is expensive or when referential equality matters for downstream components.

// ❌ Pointless - this is cheaper to just run
const fullName = useMemo(() => `${first} ${last}`, [first, last]);

// ✅ Worth it - heavy computation, or passed to a memoized child
const sortedList = useMemo(
  () => largeArray.sort((a, b) => a.score - b.score),
  [largeArray]
);

const handleSubmit = useCallback(() => {
  processOrder(cart, userId);
}, [cart, userId]);
Enter fullscreen mode Exit fullscreen mode

Pair these with React.memo() on child components that receive stable props, so they bail out of re-renders entirely:

const ProductCard = React.memo(({ product, onAddToCart }) => {
  return (
    <div>
      <h3>{product.name}</h3>
      <button onClick={onAddToCart}>Add to Cart</button>
    </div>
  );
});
Enter fullscreen mode Exit fullscreen mode

Pro tip for 2026: Use the React DevTools Profiler's "Why did this render?" feature to identify which components are actually causing performance issues before you reach for memoization. Don't guess - measure.


2. Code Split Aggressively with React.lazy and Route-Based Chunking

Shipping your entire app in one bundle is one of the biggest performance sins a React developer can commit. With React's built-in lazy and Suspense, route-based code splitting is now trivial - yet many codebases still don't do it properly.

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

// Each page is its own chunk - loaded only when the route is visited
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/analytics" element={<Analytics />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}
Enter fullscreen mode Exit fullscreen mode

Go further by lazy-loading heavy components within pages - rich text editors, chart libraries, map integrations, and modals that aren't immediately visible on load are all excellent candidates:

const RichEditor = lazy(() => import('./components/RichEditor'));
const ChartPanel = lazy(() => import('./components/ChartPanel'));

// Only loads the bundle when the user clicks "Edit"
{isEditing && (
  <Suspense fallback={<EditorSkeleton />}>
    <RichEditor content={post.body} />
  </Suspense>
)}
Enter fullscreen mode Exit fullscreen mode

2026 tip: If you're on Next.js (which most serious React projects are), use next/dynamic with ssr: false for client-heavy widgets to eliminate their server-side overhead entirely.


3. Virtualize Long Lists with @tanstack/react-virtual

If your app renders lists - product catalogs, transaction histories, data tables, chat threads - and you're not virtualizing them, you're rendering potentially thousands of DOM nodes that the user will never see. This tanks both initial paint time and scroll performance.

@tanstack/react-virtual (the successor to react-window) is the 2026 standard for this:

npm install @tanstack/react-virtual
Enter fullscreen mode Exit fullscreen mode
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';

function ProductList({ products }) {
  const parentRef = useRef(null);

  const virtualizer = useVirtualizer({
    count: products.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 80, // estimated row height in px
    overscan: 5,            // render 5 items outside the viewport for smooth scroll
  });

  return (
    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            <ProductCard product={products[virtualItem.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Instead of rendering 10,000 rows, the virtualizer renders only what's in the viewport - typically 10 to 20 items. The performance difference on a large dataset is not subtle; it's transformative.

When to use it: Any list with more than 100 items is a candidate. Any list with more than 500 items is a requirement.


4. Optimize State Architecture - Collocate State and Avoid Context Overuse

One of the most overlooked performance killers in React is poor state architecture. Specifically: storing everything in a top-level context or a giant global state causes the entire tree to re-render whenever anything changes.

The fix is state collocation - move state as close to where it's used as possible.

// ❌ Global context re-renders the entire app on every keystroke
const AppContext = createContext();

function App() {
  const [searchQuery, setSearchQuery] = useState('');
  // searchQuery change re-renders everything subscribed to AppContext
  return (
    <AppContext.Provider value={{ searchQuery, setSearchQuery }}>
      <Header />
      <Sidebar />
      <MainContent />
      <Footer />
    </AppContext.Provider>
  );
}

// ✅ Collocated - only SearchBar re-renders on keystroke
function SearchBar() {
  const [query, setQuery] = useState('');
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
Enter fullscreen mode Exit fullscreen mode

When you do need global state, split your contexts by domain so updates in one slice don't re-render consumers of another:

// Instead of one big AppContext, use domain-specific contexts
<AuthContext.Provider value={authState}>
  <CartContext.Provider value={cartState}>
    <ThemeContext.Provider value={themeState}>
      <App />
    </ThemeContext.Provider>
  </CartContext.Provider>
</AuthContext.Provider>
Enter fullscreen mode Exit fullscreen mode

For serious apps in 2026, consider Zustand or Jotai over React Context for shared state. They're lightweight, selector-based (components only re-render when their specific slice changes), and have no provider boilerplate.


5. Defer Non-Critical Work with useTransition and useDeferredValue

React 18's Concurrent Features are widely available and still underutilized. useTransition and useDeferredValue let you tell React: "this update isn't urgent - keep the UI responsive and get to it when you can."

This is a game changer for search inputs, filter UIs, and heavy data transformations.

useDeferredValue - for expensive renders triggered by fast-changing input:

import { useDeferredValue, useState } from 'react';

function SearchPage({ allProducts }) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  // This filter runs on the deferred value - the input stays snappy
  const filteredProducts = allProducts.filter(p =>
    p.name.toLowerCase().includes(deferredQuery.toLowerCase())
  );

  return (
    <>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search products..."
      />
      {/* filteredProducts uses deferredQuery, so the list update is non-blocking */}
      <ProductList products={filteredProducts} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

useTransition - for marking state updates as non-urgent (e.g., tab switching, heavy page transitions):

import { useTransition, useState } from 'react';

function TabView({ tabs }) {
  const [activeTab, setActiveTab] = useState(tabs[0]);
  const [isPending, startTransition] = useTransition();

  function handleTabChange(tab) {
    startTransition(() => {
      setActiveTab(tab); // React can interrupt this if the user does something more urgent
    });
  }

  return (
    <div>
      <nav>
        {tabs.map(tab => (
          <button key={tab.id} onClick={() => handleTabChange(tab)}>
            {tab.label}
          </button>
        ))}
      </nav>
      {isPending ? <LoadingSpinner /> : <TabContent tab={activeTab} />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The key insight: by marking the tab switch as a transition, React won't freeze the UI while the new tab renders. If the user clicks another tab immediately, React will abandon the previous render and start the new one. This is fundamentally different from anything you could do in React 17 or earlier.


Wrapping Up

These five tricks address the most common performance bottlenecks in real-world React apps in 2026:

Trick What It Solves
Surgical memoization Unnecessary re-renders
Code splitting Bloated initial bundle
List virtualization DOM overload on long lists
State collocation Context-triggered cascading re-renders
Concurrent Features UI blocking on expensive updates

Performance optimization is an iterative process. The best workflow is: measure with DevTools → identify the actual bottleneck → apply the right fix → measure again. Don't pre-optimize, but don't ignore the data either.

Ship fast. Iterate faster.


Found this helpful? Share it with your team. React performance is a team sport.

Top comments (0)