Front-End Development React Optimization 101: Tips and Tricks Integrating Cloudinary with Astro Building an Image Upload Feature with JavaScript Mastering Image Alignment: Centering Images with HTML & CSS Adding Video to Your React Native App with react-native-video HTML Image Slider: Do It Yourself and 1-Step Image Gallery Widget How to Effectively Manage Digital Assets in a PHP Image Gallery Introducing Angular Image Editor: Your New Editing Too Mastering Javascript Image Annotation Mastering JavaScript Image Popup Python Video Player: 3 Free Options and a Quick Tutorial Image Recognition Machine Learning: Use Cases and Common Algorithms HTML/CSS: How to Center Images Vertically and Horizontally How to Create an Image Map Understand CSS Background Position with 4 Simple Examples Java for Image Processing: 4 Libraries You Should Know Python Video Processing: 6 Useful Libraries and a Quick Tutorial Blur Image CSS: Two Ways to Blur Images for Gorgeous Effects Designing a Video Flipping App for Android Build an App for Embedding Video Watermarks on Android Devices Change Image on Hover with HTML and CSS How to Align Images with CSS Full Page Background Image with CSS: Tutorial and 5 Automation Tips Using CSS to Scale Page Elements and a Better Way to Scale Your Images CSS Background Image: Quick Tutorial and 3 Automation Tips Featured Image: Best Practices to Feature Images on Your Website Image Gallery Websites: Tips and Tricks for a Stunning Image Gallery 6 Ways to Stretch a Background Image with CSS Auto Cropping for Images and Video: Features & Best Practices FLAC vs. WAV: 4 Key Differences and How to Choose Converting Audio to Video: A Practical Guide FLAC vs. AIFF: 5 Key Differences and How to Choose FLAC vs. MQA: 5 Key Differences and How to Choose Converting WAV Files To OGG The Ultimate Guide On Converting OGG Files To WAV Sound Choices: FLAC vs. MP3 AAC vs MP3 – The Future of Audio Files All about AIFF and how it compares to WAV and MP3 Integrating Cloudinary with Netlify Integrating Cloudinary with Svelte and SvelteKit Integrating Cloudinary with Nuxt Integrating Cloudinary with Gatsby File Upload as a Service: How It Works and 5 Leading Solutions Native Mobile App Development Creative Uses for CSS Inner Border and 3 Ways to Set a Border Integrating Cloudinary with Next.js Front-End Development: The Complete Guide

React Optimization 101: Tips and Tricks

React optimization

Tons of sites rely on the power of React, but how can you ensure your React Optimization is at its most effective? React is a popular JavaScript framework for building user interfaces for web, mobile, and desktop applications. In a recent StackOverFlow Developer Survey conducted in May 2024, React tops the list of the most popular front-end web frameworks used by developers.

While React is a popular choice for building high-performance web applications, it can become a significant performance bottleneck if not used properly. That’s why React Optimization is so essential–without it, you’ll deliver a poor, buggy, and slow experience that can drive users away from your site.

In this article, we’ll explore various techniques and strategies you can use to enhance the performance of your React applications. Ready to dive in? Let’s get started!

In this article:

Six Techniques for React Optimization

As a developer, it’s very easy to make unintended mistakes in the organization or structure of your React applications, leading to performance problems that may not be easily identifiable. Let’s take a look at some pitfalls you should avoid and React optimization techniques you can implement to make your applications more efficient.

#1. Avoid Mutating State

Mutating state directly in a React application can lead to performance issues, bugs, and unintended side effects. Props and states in React are immutable. When you mutate props and state directly, React may not recognize that the prop or state has changed, leading to inconsistent output and outdated UI.

The following examples from the React docs show how you should approach updating props and state in your apps:

When updating props, don’t do this:

function Post({ item }) {
  item.url = new Url(item.url, base); // 🔴 Bad: never mutate props directly
  return <Link url={item.url}>{item.title}</Link>;
}

Do this instead:

function Post({ item }) {
  item.url = new Url(item.url, base); // 🔴 Bad: never mutate props directly
  return <Link url={item.url}>{item.title}</Link>;
}

Likewise, when updating state, you should use the setter function returned by the useState hook and avoid mutating the state directly.

Don’t do this:

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

  function handleClick() {
    count = count + 1; // 🔴 Bad: never mutate state directly
  }

  return (
    <button onClick={handleClick}>
      You pressed me {count} times
    </button>
  );
}

But do this instead:

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

  function handleClick() {
    setCount(count + 1); // ✅ Good: use the setter function returned by useState
  }

  return (
    <button onClick={handleClick}>
      You pressed me {count} times
    </button>
  );
}

#2. Implement Code Splitting

Code splitting is a programming technique that breaks down a web application’s code into smaller chunks and loads them on demand, which can improve performance. Imagine a React app with multiple routes ( or pages). Technically, you might not need to show users all the components during the initial page load.

For example, a user visiting the homepage doesn’t need the code for the admin dashboard immediately. Code splitting allows you to load each route’s code only when the user visits that specific route.

React supports code splitting with dynamic import() statements, which allows you to dynamically load modules at runtime, and React.lazy, a React API used in conjunction with the Suspense component to use lazy loading.

Here’s an example of dynamic import in React:

import("./math").then(math => {
  console.log(math.add(16, 26));
});

Below is another example of how to implement code splitting using React.lazy and Suspense:

import React, { Suspense } from 'react';

const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
  return (
    <div>
      <h1>Welcome to My App</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

export default App;

#3. Use Memoization

Memoization is an optimization technique in computer programming that involves caching the result of an operation and returning the cached result when the same operation is performed again. In React optimization, memoization can be applied to components that are pure (a component that returns the same output if its props, state, and context are unchanged).

React components aren’t always pure in many scenarios because of state, which is essential in many dynamic web applications. To memoize a React component, we can wrap it in a memo function as follows:

const MemoizedComponent = memo(MyComponent, arePropsEqual?)

Let’s take a look at the sample code below:

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

const MemoizedText = memo(({ text }) => {
  console.log(`Rendering MemoizedText component with text: "${text}"`);

  return (
    <p>{text}</p>
  );
}, (prevProps, nextProps) => prevProps.text === nextProps.text);

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const stableText = "This text never changes";
  console.log("Re-rendering the parent component...")
  return (
    <div>
      <MemoizedText text={stableText} />
      <button onClick={() => setCount(count + 1)}>
        Increment({count})
      </button>
    </div>
  );
};

export default ParentComponent;

When you run the code above, during the initial rendering, the two console.log() statements “Re-rendering the parent component…” and “Rendering MemoizedText component with text: “This text never changes“, are printed in the terminal.

However, when we increment the counter, only the text “Re-rendering the parent component…” is printed to stdout. This is because when we click the “Increment” button, the ParentComponent re-renders, but the MemoizedText component does not re-render because its text prop has not changed.
React optimization

#4. Virtualize Long Lists

Virtualization, also known as windowing, is a React optimization technique that shows only a part of a large dataset, such as a long list or table, in the viewport of a screen. This reduces the time it takes to re-render components and the number of DOM nodes created.

react-window and react-virtualized are two commonly used libraries for virtualization in React. Here’s a simple example of windowing using the react-window library to render a large list of items:

import { FixedSizeList as List } from 'react-window';


const Row = ({ index, style }) => (
  <div style={style}>Row {index}</div>
);


const Example = () => (
  <List
    height={150}
    itemCount={1000}
    itemSize={35}
    width={300}
  >
    {Row}
  </List>
);

#5. Lazy Load Images and Videos

Images, videos, fonts, and other digital assets are among the most common causes of performance concerns in web applications today. Large and uncompressed resources can have a significant impact on web app performance, particularly for users with low-end devices and poor internet connections.

Compressing and optimizing digital files and lazy-loading, caching, or sending assets via a CDN is critical for providing an enhanced user experience. Lazy loading and delivering content via a Content Delivery Network (CDN) through a media asset solution such as Cloudinary are effective strategies for improving app performance and user experience.

Apart from delivering content through a CDN, Cloudinary integrates well with React by providing additional optimization benefits such as optimizing images and videos quality on the fly and delivering assets in the correct format optimal for performance.

Here’s an example from the Cloudinary React SDK docs showing how to use the lazyload and responsive plugins to lazy load images:

import React from 'react'
import {Cloudinary} from "@cloudinary/url-gen";

// Import plugins
import {AdvancedImage, lazyload} from '@cloudinary/react';

 const App = () => {

  // Create and configure your Cloudinary instance.
  const cld = new Cloudinary({
    cloud: {
      cloudName: 'demo'
    }
  }); 

  // Use the image with public ID, 'docs/colored_pencils'.
  const myImage = cld.image('docs/colored_pencils');

  // Use the lazyload plugin
  return (
    <div>
      <AdvancedImage cldImg={myImage} plugins={[lazyload()]}/>
    </div>
  )
};

#6. Cut Down the Size of Imported Packages

Performing application-wide audit, removing unused dependencies and dead code through tree shaking, minification, and so on, in a React project can be useful for keeping the project lightweight, reducing potential security risks, and improving performance.

For example, you can use a tool like depcheck to find unused dependencies in your project. The Import Cost extension in VS Code also displays the size of each imported package in your project, which can be useful in choosing lightweight alternatives.

Getting the Most From Your App with React Optimization

While React was developed to be efficient by default, it can still benefit greatly from a variety of optimization strategies for optimal performance. By leveraging strategies such as code splitting, lazy loading, memoization, windowing, and CDNs, etc., you can improve your application load speed, avoid wasteful re-renders, and maximize resource efficiency.

Luckily, with a solution like Cloudinary, you get to enjoy some of these optimization benefits automatically without writing complex code. Feel free to sign up for a free account and explore the Cloudinary React SDK documentation today to get started.

QUICK TIPS
Colby Fayock
Cloudinary Logo Colby Fayock

In my experience, here are tips that can help you better optimize your React applications:

  1. Batch state updates to minimize re-renders
    React automatically batches state updates in event handlers, but outside of these (e.g., in promises or timeouts), multiple state updates cause unnecessary re-renders. Use unstable_batchedUpdates from React DOM to manually batch updates outside of event handlers.
  2. Use React.PureComponent for automatic shallow prop checks
    If your components only depend on shallow prop comparisons, use React.PureComponent instead of React.Component to avoid unnecessary re-renders. This is an easier alternative to manually using shouldComponentUpdate for simple cases.
  3. Use dynamic imports for large libraries
    For large libraries such as lodash, import only the functions you need. Instead of import _ from 'lodash', use import debounce from 'lodash/debounce' to avoid loading the entire library unnecessarily, reducing bundle size.
  4. Throttle or debounce expensive operations
    If you have functions that trigger on frequent user interactions (e.g., search bars, scrolling), use throttling or debouncing to limit how often they execute. Libraries like lodash.debounce can help prevent performance bottlenecks in components.
  5. Leverage React’s useCallback for stable function references
    Functions in React components are re-created on every render, which can cause unnecessary re-renders in child components that receive these functions as props. Use useCallback to memoize functions and pass stable references to child components.
  6. Use Suspense with fallback components for seamless loading
    To improve the user experience during lazy loading, make use of Suspense with well-designed fallback components. The fallback should be lightweight and informative to keep users engaged during loading times.
  7. Prerender critical pages for faster time-to-interactive
    Use prerendering techniques (e.g., Next.js’s Static Site Generation or Server-side Rendering) to precompute HTML for critical pages. This reduces the time-to-interactive for first-time visitors and improves overall page load performance.
  8. Keep component trees shallow
    Deeply nested component trees can slow down rendering. Keep the component hierarchy as flat as possible by breaking large components into smaller, reusable pieces and using React’s composition model to simplify the tree.
  9. Profile performance using React Developer Tools
    Use React’s Profiler to analyze components that are re-rendering unnecessarily. Identify “hot” components that are causing performance degradation, and address these by applying memoization or splitting components.
  10. Reduce third-party dependencies in production
    Periodically audit and remove unnecessary third-party libraries, especially those included during development but not used in production. Bundle size tools like Webpack Bundle Analyzer can help identify large dependencies, helping you decide whether to remove or replace them.
Last updated: Oct 15, 2024