DEV Community

Cover image for Performance Optimization using React profiler, Memo, useMemo and useCallback
Ugwuoke Adaeze
Ugwuoke Adaeze

Posted on

Performance Optimization using React profiler, Memo, useMemo and useCallback

Code Performance optimization is a set of strategies and techniques developers utilize to enhance the speed and efficiency of their software applications. There are various techniques software developers use to enhance performance optimization, and we will be discussing a few of them below.
Every time a component’s state or props change, React triggers a re-render to update the user interface. However, unnecessary re-renders can hurt performance, especially if the component tree is large.
Example; If ParentComponent passes a prop to ChildComponent, every time the parent re-renders, the child also re-renders, even if the child’s data hasn’t changed.

<Profiler />
The <Profiler /> component is a built-in React tool that measures the rendering behavior of components wrapped inside it. It records the response time of a component and logs each re-render with useful information such as render duration and the reason for re-renders (e.g., state changes or prop updates). Profiling adds some additional overhead, so it is disabled in the production build by default.
<Profiler /> takes in two props, an id, and an onRender callback function.
<Profiler id="Sidebar" onRender={onRender}>
<Profiler/>

  • id: A string identifying the part of the UI you are measuring, usually the name of the component being measured.
  • onRender: An onRender callback that React calls every time components within the profiled tree update. It receives information about what was rendered and how much time it took.
    function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime) {
    // Aggregate or log render timings...
    }

    usage:

  • The whole App can wrapped with the profiler as shown below

<Profiler id="App" onRender={onRender}>
  <App />
</Profiler>
Enter fullscreen mode Exit fullscreen mode
  • Wrapping specific components with the profiler
<App>
  <Profiler id="Sidebar" onRender={onRender}>
    <Sidebar />
  </Profiler>
  <PageContent />
</App>
Enter fullscreen mode Exit fullscreen mode
  • Nesting components with the profiler
<App>
    <Sidebar />
    <Profiler id="Content" onRender={onRender}>
    <Content>
      <Profiler id="Editor" onRender={onRender}>
        <Editor />
      </Profiler>
      <Preview />
    </Content>
  </Profiler>
</App>
Enter fullscreen mode Exit fullscreen mode

Memo
React memo is a higher-order component (HOC) that wraps your functional component. memo is usually used with pure components, that is components that don't rerender unless there is a change in the props or states. This helps to avoid unnecessary re-renders when the parent component updates.
To memoize a component wrap it in a memo, it will not re-render when its parent component re-renders as long its props remain the same.

import { memo, useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

const Greeting = memo(function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
});
Enter fullscreen mode Exit fullscreen mode

useMemo
useMemo is a React hook that caches/stores the result of a function so that the value doesn’t get recalculated unless its dependencies change. This is useful for expensive calculations that don’t need to run on every render.

import React, { useState, useMemo } from 'react';

// Expensive function
const expensiveCalculation = (num) => {
console.log("Calculating...");
  for (let i = 0; i < 1000000000; i++) {} // more logics 
  return num * 2;
};

const App = () => {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState("");

  // Memoizing the result of expensiveCalculation
  const memoizedValue = useMemo(() => expensiveCalculation(count), [count]);

  return (
    <div>
      <h1>Memoization Example</h1>
      <p>Result: {memoizedValue}</p>

      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="placeholder value"
      />
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

useMemo ensures that the expensiveCalculation only runs when count changes.

useCallback
useCallback is a React hook that memoizes a function so that it isn’t recreated every time the component re-renders. This is particularly useful when you pass functions as props to child components.

import React, { useState, useCallback } from 'react';

const Child = React.memo(({ onClick }) => {
  console.log("Child component re-rendered");
  return <button onClick={onClick}>Click Me</button>;
});

const App = () => {
  const [count, setCount] = useState(0);

  // Memoizing the function to prevent re-creation
  const handleClick = useCallback(() => {
    console.log("Button clicked");
  }, []);

  return (
    <div>
      <h1>useCallback Example</h1>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <Child onClick={handleClick} />
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Without useCallback, the handleClick function would be recreated every time the parent component re-renders, causing the Child to re-render unnecessarily.
With useCallback, the function is memoized, so the Child only re-renders when necessary.

When to Use These Tools?

  1. Use React.memo: When you have functional components that receive unchanging props. Example: Display components that show static or rarely changing information.
  2. Use useMemo: When you need to cache expensive calculations to avoid unnecessary recomputations. Example: A search feature that filters large datasets.
  3. Use useCallback: When you pass functions as props to child components to avoid triggering re-renders. Example: Buttons or input handlers inside child components.
  4. Use <Profiler /> Component
  • During development: Use it to track performance bottlenecks early and ensure components aren’t rendering more than necessary.
  • In large applications: Use to analyze the impact of heavy components (e.g., data grids or charts).
  • Before releasing: Identify any slow renders or unnecessary re-renders before shipping your app.

Other methods of performance optimization include; code splitting and lazy for lazyloading.

Code splitting

Code-splitting is a feature that enables you to create multiple bundles that can be dynamically loaded at runtime. This basically breaking your application’s code into smaller chunks (bundles).
Instead of loading the entire app upfront, code splitting ensures that only the necessary chunks are loaded at a time—like loading a particular page or component on demand.
This reduces initial load time and improves performance, especially for large applications.
Code splitting can help your app “lazy-load” just the things that are currently needed by the user, which can significantly improve the speed and performance of your app. While you haven’t reduced the overall amount of code in your app, you’ve avoided loading code that the user may never need and reduced the amount of code needed during the initial load.

Lazy
lazy allows you to defer loading a component’s code until it is rendered for the first time. It returns a React component you can render in your tree. While the code for the lazy component is still loading, attempting to render it will be suspended. can be used to display a loading indicator while it’s loading.

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

// Lazy load pages
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));

function App() {
  return (
    <Router>
      <Suspense fallback={<p>Loading page...</p>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

We use lazy() to load the page components (Home, About, Contact) only when the user navigates to those routes and <suspense/> to show a loading message while the loads.

Happy Learning!!!

Top comments (0)