Optimize your React App

Hello folks,
In the current tech world, every startup is going for the component-based UI library that is React there are a lot of component-based libraries, but people choose React because this is a maintainable and quick setup along with this, it has faster render and has different algorithm to update the
DOM using the Diffing Algorithm,
There are many ways to optimize the React app, I am going to tell about this in steps:

Photo by Lautaro Andreani on Unsplash

#1 Import statement in React or Lazy Loading(React.lazy)

Change the import statements of the components into the react. Try to use the lazy import in the react, the benefit of the lazy loading is,

Before using the lazy component (Syntax without lazy loading)

import React from 'react'
import Card from "../Card"
const App = () => {
    return (
        <div>
            <h1>This is an Demo</h1>
            <Card />
        </div>
    )
}

export default App;

a)It reduces the bundler size by using code splitting and making chunks.
b)It faster the loading and makes the user experience good, it loads the component and its run-time only
c)It benefits the memory of the javascript browser, it uses less memory because the required content is being loaded only.
d)it reduces the bandwidth by loading the necessary resources and can be useful if the user is facing a slow internet connection. because it loads the minimum required resource rather than loading a complete resource

Using the lazy component syntax with suspense

import React, { lazy, Suspense } from 'react'
const Card = lazy(() => import('./Card'))
const App = () => {
    return (
        <div>
            <h1>This is an Demo</h1>
            <Suspense fallback={<div>Loading...</div>}>
                <Card />
            </Suspense>
        </div>
    )
}

export default App

The Suspense is a component that React provides to handle asynchronous operations such as lazy loading components, it refers to an edge or boundary to show the loading or spinner before the actual content is loaded.

#2 Check for the irrelevant renders of the components(React.memo)

when you are working with the hooks and passing the state from one component to another via props. The behavior of React is to re-render the component and change the DOM by comparing the previous state to the updated state, by comparing both the components React manipulates the changed properties to save time, and efficiency and renders the component faster this is also known as diffing. it is a type of algorithm where React compares the change and updates the DOM (known as the Diffing algorithm.)

For example

import  { useState } from "react";

const Card = (() => {
  console.log("re-render Card");
  return <div>Card</div>;
});

const App = () => {
  const [count, setCount] = useState<number>(0);
  return (
    <div>
      <h1>This is an App component </h1>
      <h2>This count is: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Card />
    </div>
  );
};

export default App;

In the above example whenever the state of the count is changed then the Card component gets re-render You can see this in this console also.
this is happening because react re-renders the complete component once the state of any other element gets changed so if the count changes the complete <App/> component gets re-render, and indirectly the <Card/>component gets re-render. Eventually, the <Card/> component has not changed any state thus it gets rendered. Assume the <Card/> is a complex component that gets rendered repeatedly if there are no changes in that component so it causes a performance issue.
To avoid such kinds of issues we can use the in-built React hook that is React.memo this hook is used to memorize the component
whenever the count is changed and the <Card/>component has no effect on the count this it memorize the complete component and stop the re-rendering of that component to faster the performance.
Reference: react.dev/reference/react/memo Do checkout this link to learn more about it.
For example:

import React, { useState } from "react";

const Card = React.memo(() => { //This is memo hook
  console.log("re-render Card");
  return <div>Card</div>;
});

const App = () => {
  const [count, setCount] = useState<number>(0);
  return (
    <div>
      <h1>This is an App component </h1>
      <h2>This count is: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Card />
    </div>
  );
};

export default App;

#3 Memorise the expensive calculations (React.useMemo)

React.useMemo is a hook provided by React that helps optimize performance by memoizing complex calculations. It allows React to cache the result of an expensive computation and reuse it during re-renders, instead of recalculating it every time.

By default, React re-renders everything inside a component whenever the component updates. However, with useMemo, we can compute a value once and reuse it as long as the dependencies remain unchanged, leading to faster rendering and better efficiency.

For more details, check the official documentation: React.useMemo

Example: Without using useMemo( )

import React, { useState } from "react";

const ExpensiveCalculation = ({ num }) => {
  console.log("Calculating...");
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += num;
  }
  return <p>Result: {result}</p>;
};

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

  return (
    <div>
      <h2>Without useMemo</h2>
      <button onClick={() => setCount(count + 1)}>Re-render ({count})</button>
      <ExpensiveCalculation num={num} />
    </div>
  );
};

export default App;

👉 Issue: Every time the button is clicked, the expensive calculation runs again, even though num hasn’t changed, Run the code and check for the Profiler section of the React dev tool

Checkout the all three renders and three stages,

Checkout the re-render time of the expensive functions ExpensiveCalculations(),

Example: Without using useMemo( )

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

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

  const memoizedValue = useMemo(() => {
    console.log("Calculating...");
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += num;
    }
    return result;
  }, [num]); // Only recalculates if `num` changes

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

export default App;

👉 Benefit: Now, the expensive calculation only runs when num changes. Clicking the button no longer triggers unnecessary computations, improving performance.

Checkout the time of re-render decrease with 100x

The initial render takes time because it was calculated the first time, then the next render is almost 100x faster after the use useMemo,

#4 Cache the complex functions (React.useCallback( ))

Whenever a re-render happens then function definitions are redefined if the function is being passed as a props. to avoid this we can utilise the useCallback hook provided by the hook. useCallback is a React hook used to memoize functions, preventing unnecessary re-creations during re-renders. This is especially useful when passing functions as props to child components, as it helps avoid unnecessary re-renders of those children.
👉 Issue: Every time the child re-renders then it changes the function definitions if we pass the function as a prop, thus useCallback ensuring that the function definition should not redefine it is already present.

Example: Without using useCallback( )

import React, { useState } from "react";

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

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

  const handleClick = () => {
    console.log("Button clicked");
  };

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

export default App;

Example: With useCallback( )

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

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

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

  const handleClick = useCallback(() => {
    console.log("Button clicked");
  }, []); // Function reference remains stable

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

export default App;

use can check the difference between both components in the console.

#5. Optimize API Calls with React Query or SWR

if you are using the native approach for calling the API then you need to optimize your approach, In the current web3, we are focusing on the faster delivery of the content and providing a smooth web flow to our web app. Instead of using the fetch( ) are axios we can combine this method to cache or optimize API calling way that is React Query by TanStack . we API calling can be easily manageable and you don’t have to manage all the state and caching mechanisms in the app, All the things are managed by the ReactQuery for example pagination, loading, error, caching stale time, infinite calling, suspense calling and many more.
install and docs.

npm i react-query

import { useQuery } from "react-query";

const { data, error } = useQuery("users", fetchUsers);