DEV Community

Cover image for ReactJS Performance Optimization: Best Practices
Sarthak Chatterjee
Sarthak Chatterjee

Posted on

ReactJS Performance Optimization: Best Practices

In today’s fast-paced digital world, users expect websites and applications to be lightning-fast and highly responsive. For developers working with ReactJS, ensuring optimal performance is crucial to providing a seamless user experience.
In this blog, we’ll explore simple yet effective strategies to boost the performance of your ReactJS applications. Whether you’re a beginner or a seasoned developer, these tips will help you write cleaner, faster, and more efficient React code. Let’s dive in!

1. Optimizing Large Imports in React with Lazy Loading

When working with large files in a React project, importing them directly into a component can cause significant performance issues. The application may become sluggish, especially when rendering the component for the first time.

The Problem

Consider the following example where a large JSON file is imported into a component:

// components/MyComponent.js
import React from "react";
import largeData from "../data/largeData.json";

const MyComponent = () => {
  return (
    <div>
      <h2>Large Data</h2>
      <pre>{JSON.stringify(largeData, null, 2)}</pre>
    </div>
  );
};

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

And in the main file:

import React from "react";
import MyComponent from "./components/MyComponent";

const App = () => {
  return (
    <div>
      <h1>My App</h1>
      <MyComponent />
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Since MyComponent is imported directly, the large JSON file is loaded immediately, potentially slowing down the initial render of the app.

The Solution: Lazy Loading with React.lazy()

A better approach is to use lazy loading. This ensures that MyComponent (and the large JSON file) is only loaded when needed.

Optimized Code

Modify the App.js file to use React.lazy() and Suspense:

import React, { Suspense } from "react";

const LazyComponent = React.lazy(() => import("./components/MyComponent"));

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

export default App;
Enter fullscreen mode Exit fullscreen mode

How This Helps

  1. Reduces Initial Load Time: The component and large JSON file are only loaded when they are needed.

  2. Improves Performance: The main bundle remains small, making the app more responsive.

  3. Enhances User Experience: The fallback UI (Loading...) ensures that the user sees something while waiting.

  4. Efficient Resource Management: Suspense ensures that resources are loaded only when required, preventing unnecessary memory usage and improving overall app performance.

By implementing lazy loading, we significantly enhance our React app's performance, making it more efficient and user-friendly.

2. Using useMemo in React

Imagine you are a student preparing for an exam. You have a big book with 500 pages, and you need to find the same answer again and again. Instead of reading through the entire book each time, you decide to write down the answer on a sticky note so that you can quickly check it when needed. This simple trick saves you a lot of time and effort.

In the world of React, useMemo does something similar—it remembers the result of a function so that React doesn’t have to recalculate it every time a component renders.

What is useMemo?

useMemo is a React Hook that helps optimize performance by memoizing a computed value. It prevents expensive calculations from running on every render unless the dependencies change.

The Problem: Expensive Calculations

Consider this example where we have a function that performs a heavy calculation every time the component renders:

import React, { useState } from "react";

const slowFunction = (num) => {
  console.log("Running slow function...");
  for (let i = 0; i < 1000000000; i++) {} // Simulating a slow calculation
  return num * 2;
};

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

  const result = slowFunction(number);

  return (
    <div>
      <h1>useMemo Example</h1>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Re-render</button>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

In this case, even when we update count, the slowFunction runs again unnecessarily.

Image description

Optimizing with useMemo

We can use useMemo to ensure that the function only runs when number changes:

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

const slowFunction = (num) => {
  console.log("Running slow function...");
  for (let i = 0; i < 1000000000; i++) {}
  return num * 2;
};

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

  const result = useMemo(() => slowFunction(number), [number]);

  return (
    <div>
      <h1>useMemo Example</h1>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Re-render</button>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

How This Helps

  1. Improves Performance: useMemo ensures the expensive function only runs when necessary.
  2. Avoids Unnecessary Computations: It caches the result and prevents re-execution unless dependencies change.
  3. Enhances User Experience: Faster renders make the app feel more responsive.

Just like saving important answers on sticky notes for quick reference, useMemo helps React remember the answers to calculations, making your application smoother and more efficient.

For reference, you can find the complete code in this GitHub repository: Learning-useMemo-in-React. 🚀

3. Using useCallback in React

Imagine you love ordering pizza from your favorite restaurant. Every time you place an order, you call the restaurant and repeat your address. But wouldn’t it be easier if the restaurant remembered your address so you didn’t have to say it every time? That’s exactly what useCallback does in React—it helps React remember functions so that they don’t get re-created unnecessarily.

What is useCallback?

useCallback is a React Hook that memoizes a function, meaning it returns the same function instance between renders unless its dependencies change. This optimization is especially useful when passing functions as props to child components.

The Problem: Unnecessary Function Re-Creation

Consider this example where a function is created inside a component on every render:

import React, { useState } from "react";

const Button = ({ handleClick }) => {
  console.log("Button component re-rendered");
  return <button onClick={handleClick}>Click me</button>;
};

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

  const handleClick = () => {
    console.log("Button clicked");
    setCount(count + 1);
  };

  return (
    <div>
      <h1>useCallback Example</h1>
      <p>Count: {count}</p>
      <Button handleClick={handleClick} />
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

What’s Wrong?

  • The handleClick function is re-created every time App re-renders.
  • The Button component unnecessarily re-renders even though its logic hasn't changed.

Optimizing with useCallback

We can use useCallback to ensure that handleClick is only re-created when count changes:

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

const Button = ({ handleClick }) => {
  console.log("Button component re-rendered");
  return <button onClick={handleClick}>Click me</button>;
};

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

  const handleClick = useCallback(() => {
    console.log("Button clicked");
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <h1>useCallback Example</h1>
      <p>Count: {count}</p>
      <Button handleClick={handleClick} />
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

How This Helps

  1. Prevents Unnecessary Function Re-Creation: useCallback ensures handleClick remains the same across renders unless count changes.
  2. Optimizes Child Component Rendering: Button doesn’t re-render unnecessarily because the function reference remains the same.
  3. Improves Performance: Helps optimize React apps by reducing unnecessary re-renders.

Just like how a restaurant remembers your address to make ordering easier, useCallback helps React remember functions to make rendering more efficient. 🚀

Top comments (0)