The Ultimate Guide to React Performance Optimization: Tips and Tricks for Developers

React is a powerful library for building user interfaces, but as your application grows then complexity increases and performance will slow down if we don’t follow proper code structure and optimization techniques.In this blog we are going to discuss how we can improve the performance of your React App.
Memoization
Memoization is an optimization technique to enhance the efficiency of functions by storing the function’s value and returning the stored result when similar input is provided. This technique is especially useful when we repetitively provide same input field .. in which case it will not recalculate again and again. Hence the function will not be re-created in browser memory stack.
Memoization wrapped function only works when the input props changes.
React.memo ()
React.memo is a higher-order component (HOC). It will memoize the functional component and its props. It wraps functional components to prevent unnecessary re-renders unless the input props changes.
Let’s see now with an example:
import React, { useMemo } from 'react';
// UserList component to display the list of users
const UserList = ({ users }) => {
const result = useMemo(() => expensiveCalculation(input), [input]);
console.log('Rendering UserList');
return (
<ul>
{users.map((user, index) => (
<li key={index}>{user}</li>
))}
</ul>
);
};
// Memoized version of the UserList component
const MemoizedUserList = React.memo(UserList);
Here the UserList Component is wrapped inside the React.memo().userList Component is only re-render when the UserList props changes.Using memoization we can improve the react load performance and quicker UI and smooth interactions.
useMemo()
useMemo is a react hook that can cache the result of computational functions and recalculates only when dependency changes.Call useMemo at the top level of your component to cache a calculation between re-renders :
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
const cachedValue = useMemo(calculateValue, dependencies)
const result = useMemo(() => expensiveCalculation(input), [input]);
Here let us assume that expensiveCalculation function takes a lot of time so we can memoize the result to re-calculate only when the dependency changes.
useCallback()
useCallback is a react Hook that lets you cache a function definition between re-renders.useCallback is used to memoize the functions so that same function reference is used across re-renders unless specific dependencies changes.We can use useCallback when passing functions to child components to avoid creating new function instances on every render, which can cause unnecessary re-renders of child component.
import { Button } from 'bootstrap/dist/js/bootstrap.bundle.min';
import { useCallback } from 'react';
function MyComponent(props) {
const { data } = props;
const handleOnPress = useCallback(() => {
// do something with data
}, [data]);
return <Button onPress={handleOnPress} />;
}
Here the the handleOnPress function will only be re-recreated if the value of data changes.This can be useful if handleOnPress is passed as a prop to a child component that is re-created multiple times. So by using useCallback we can avoid re-creating the function again and again and we can improve the performance of our app.There are a few cases where using useCallback may not have any effect. If the function being memoized is only called once and never re-rendered, there is no need to use useCallback.
Lazy Loading
Lazy loading in React allows you to split your code into smaller chunks, and it will load only when it is required. This can improve the initial loading time of your application.React provides a feature called dynamic import, Suspense component to achieve the lazy loading. You can implement lazy loading for a route in a React application using React.lazy() and Suspense.
lazy()
It is used to dynamically import components when they required. b) SuspenseIt is a component provided by React it will lets you “wait” for the dynamic import to load..meanwhile it will show fallback UI (“Loading..” or custom UI). Now let’s see an example :Create a separate file for the component you want to lazy load.
import React from 'react';
const Products = () => {
return <div>This is a lazy-loaded component!</div>;
};
export default Products;
Here we are lazy loading the Products Component.Use React.lazy() to import the component dynamically in your route file.
const Prodcuts = React.lazy(() => import("./components/products/Products"));
import React, { Suspense } from 'react';
const Prodcuts = React.lazy(() => import("./components/products/Products"));
function App() {
return (
<div>
<h1>App with Lazy Loaded Component</h1>
<Suspense fallback={<div>Loading...</div>}>
<Prodcuts/>
</Suspense>
</div>
);
}
Here Products will only be loaded when it’s needed, It will reduce the initial load time in your react app.
Avoid Inline Functions in JSX
What are Inline Functions ?
Inline functions are one that can be defined inside the JSX elements.
Example: When you write an Inline function in the onClick handler or other event handlers within the JSX, react will create a new function every time the component re-renders.Creating a new inline function inside JSX causes React to create a new function instance on each render which can lead to unnecessary re-renders of child components. Instead a better approach is to define the function outside the JSX or memoize them with useCallback.
Why it is a problem ?
- Unnecessary Re-rendersWhen an inline function is used inside the JSX, it gets recreated on every render, which can cause unnecessary re-renders of child components.
- Performance Hit: If you are passing the inline functions down to child components, they will receive a new function on every render, causing the child component to rerender even if the actual logic has not changed.
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Here the () => setCount(count+1) function is an inline function and is created every time the component renders.When an inline function is used in JSX, React doesn’t know that the behaviour of function is same so it treats it as a new reference and triggers a re-render of a component.If this happens frequently in large applications, it can cause unnecessary performance overhead, especially when passed down to multiple components.So defining the function outside the JSX or use useCallback()To avoid the overhead of creating new functions on every render, you can define the function outside of JSX or use the useCallback() hook to memoize the function so it doesn’t get recreated on every render.
function App() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={handleIncrement}>Increment</button>
</div>
);
}
Here handleIncrement function is defined outside of JSX so it’s not re-created on every render.
import React, { useState, useCallback } from 'react';
function App() {
const [count, setCount] = useState(0);
const handleIncrement = useCallback(() => {
setCount(count + 1);
}, [count]); // Only recreate the function if `count` changes
return (
<div>
<button onClick={handleIncrement}>Increment</button>
</div>
);
}
Using useCallback() to memoize the function will be useful when passing down the function as child components to avoid unnecessary re-renders.
Refactoring Code
What is code Refactoring ?
Refactoring means restructuring your components and code without changing the way they work. The goal is to improve the readability, performance and maintainability of your app.
React components can become complex quickly, especially when dealing with large projects.Refactoring helps break down complex logic into smaller, reusable parts.
Benefits of Refactoring Code
- Improves Readability - Clean, simple code is much easier for you and other developers to read this makes it easier to debug test and modify.
- Optimizes Performance - Refactoring can also help you identify performance and improve the efficiency of your code.
- Enhances Reusability - By breaking down your code into smaller components, you can reuse them across different parts of your app, reducing duplication.
- Boosts Maintainability - Code refactorizing helps to re-use them across different parts of your app, reducing duplication.
import React, { useState } from "react";
function CartItem({ item }) {
return (
<li>
{item.name} - {item.quantity} x ${item.price}
</li>
);
}
function Total({ total }) {
return <p>Total: ${total}</p>;
}
function ShoppingCart({ items }) {
const [total, setTotal] = useState(0);
const calculateTotal = () => {
let totalPrice = 0;
items.forEach(item => {
totalPrice += item.price * item.quantity;
});
setTotal(totalPrice);
};
const handleDiscount = () => {
setTotal(total * 0.9); // Apply 10% discount
};
const handleTax = () => {
setTotal(total * 1.05); // Apply 5% tax
};
return (
<div>
<h2>Shopping Cart</h2>
<ul>
{items.map(item => (
<CartItem key={item.id} item={item} />
))}
</ul>
<button onClick={calculateTotal}>Calculate Total</button>
<button onClick={handleDiscount}>Apply Discount</button>
<button onClick={handleTax}>Apply Tax</button>
<Total total={total} />
</div>
);
}
export default ShoppingCart;
Here is the ShoppingCart Component here we have split the CartItem Component and Total Component into separate reusable component
function CartItem({ item }) {
return (
<li> {item.name} - {item.quantity} x ${item.price} </li>
);
}
CartITem Component
function Total({ total }) {
return <p>Total: ${total}</p>;
}
We moved the rendering of individual items into a new CartItem component. Total component now renders Total price This reduces the complexity of the Shopping Cart component and makes it more readable.
Summary
React performance optimization involves techniques like memoization using React.memo() , useMemo() and useCallback(), lazy loading with React.Lazy() and Suspense, avoiding inline functions in JSX and code splitting by refactoring components to improve readability, performance and maintainability. These methods help reduce re-renders, improve load times and make react app more efficient.
Comments ()