Optimizing your Large Scale React Application
Single Responsibility Principle
Restaurant Card has a single responsibility to display Restaurant Cards on web-page
Header Component has a single responsibility to display header on web-page
So similarly for each Component that we create, we should give it a single responsibility & we should not do a lot of things in a single component. You should just break it down into small components. This is a good way of maintaining the code in a modular fashion.
It'll also become more Testable. Test Cases for small components are easier to catch.
Maintainable
Reusable
What if your Component has some extra responsibilities (As it should follow the Single Responsibility Principle)
\=>We make use of Custom Hooks to extract these extra responsibilities inside it β€οΈβπ₯ !!
(Not mandatory but it makes our code - readable, modular, reusable, testable,...)
Then our Component + Custom Hook (extra responsibilities) => Becomes more Modular
In the end, Hooks are just Normal JS functions
This will Optimize our Code. Let's see how!!
React Docs just recommends starting with use... for Custom Hooks (not mandatory) - Other Developers will know that it's a Hook (custom) and not just a function.
In ResMenu.component.ts => Currently handling API Fetch (Extract it in Custom Hook) + Displaying Restaurant Menu
As its Single Responsibility should be just to display the Menu
import Shimmer from "./Shimmer";
import { useParams } from "react-router-dom";
import useRestaurantMenu from "../utils/useRestaurantMenu";
const ResMenu = () => {
const {resId} = useParams();
const resInfo = useRestaurantMenu(resId);
if(resInfo === null) return <Shimmer/>;
const { name, cuisines , locality, avgRatingString, totalRatingsString} =
resInfo?.cards[2]?.card?.card?.info;
const {itemCards} =
resInfo?.cards[4]?.groupedCard?.cardGroupMap?.REGULAR?.cards[2]?.card?.card;
return (
<div className="menu">
<h1>{name}</h1>
<p>{cuisines}</p>
<p>{locality}</p>
<p>{avgRatingString}</p>
<p>{totalRatingsString}</p>
<h2>Menu</h2>
<ul>
{itemCards.map((item)=> <li key={item.card.info.id}>{item.card.info.name}
</ul>
</div>
);
};
export default ResMenu;
Make a separate file for each Custom Hook (Maybe in utils folder π)
useRestaurantMenu Custom Hook where we are fetching Data for Restaurant Menu
import { useEffect, useState } from "react";
import { SWIGGY_RES_URL } from "./constants";
const useRestaurantMenu = (resId) =>{
const [resInfo, setResInfo] = useState(null);
useEffect(()=>{
fetchMenu();
},[]);
const fetchMenu = async () =>{
const data = await fetch(`${SWIGGY_RES_URL}${resId}`);
const json = await data.json()
setResInfo(json?.data);
}
return resInfo;
}
export default useRestaurantMenu;
Now If something is wrong with Fetching data or you want to make any API changes you just directly have to go to this Modular useRestaurantMenu Hook and make the changes. So Good!!!
This Made everything much more Readable, Testable, Modular,...OPTIMIZED!!
We should create such Custom Hooks & make it Open Source just like useParams() which gives us the param details. Ideas: useLocalStorage() useSessionStorage. Even you as React Developers can do this!
Custom useOnlineStatus - to check internet connectivity on web app - Can be used for a Chat Applicationβ€οΈβπ₯
import { useEffect, useState } from "react";
const useOnlineStatus = () =>{
const [onlineStatus, setOnlineStatus] = useState(true);
useEffect(()=>{
window.addEventListener("offline", (event) => {
setOnlineStatus(false);
});
window.addEventListener("online", (event) => {
setOnlineStatus(true);
});
}, [])
return onlineStatus;
}
export default useOnlineStatus;
Now you can use the above custom hook wherever you want in your app π
import useOnlineStatus from "../utils/useOnlineStatus";
import ResCarousel from "./ResCarousel";
import ResNearYou from "./ResNearYou";
const Body = () =>{
const onlineStatus = useOnlineStatus();
if(onlineStatus === false) return ( <h1>Looks like your offline,
Please re-connect! Play the below game until then</h1> )
return (
<div className="main-body">
<ResCarousel/>
<ResNearYou/>
</div>
);
}
export default Body;
Make users play some games when they are offline - GOOD UX!! instead of a boring error page π
Approach to follow while creating a Custom Hook:
1. Know what input you'll need to pass to this function (can be optional)
2. Know what will be its output (return this output)
3. Implement the logic
Make sure to know about this contract of the required Hook
Now we'll see how we can Optimize your App even more
Parcel - A bundler
Main Task: Bundles all your files into 1 file (JS)
It bundled all our components into 1 JS file. This is in the Development phase so its size is more.
If it was a production build. The size would've been much less.
Is it good to have 1 JS file for everything? Good or Bad? (Good for small projects)
If there were 1000s of components in a single app. And you bundle it only in 1 JS file.
\=> The size of this JS file will be HUGE! Your JS file size increases by how many components it holds.
Your Home Page will take a lot of time to load as your JS file is so huge it will take a lot of time to load.
How to optimize this?
You can't build Production Ready Large Scale App if you won't take care of this ππ»
We will try to make smaller bundles of these files. This process is known as
**1. Chunking
2. Code Splitting
3. Dynamic Bundling
- Lazy Loading
5. On-demand loading
6. Dynamic Import**
What should be there in a smaller bundle?
A Bundle should have enough code for a major feature.
Example: Make My trip
Flight Feature: 1 Bundle
Hotels: 1 Bundle
Cabs: 1 Bundle & so on...
This is how you can logically break your Large application into smaller applications/bundles.
So that you don't put a load on a single bundle & the request for that JS file won't become so heavy that it takes a lot of time to load.
How to do this? How to build Logical Bundles in your app!
Swiggy might have a different bundle for Food Delivery & Grocery (InstaMart). Let's do the same in our app.
All Grocery related components should now come from a different bundle & a different bundle for Food Delivery.
We won't import our Grocery (Main Component of Grocery vertical) Normally.
But We'll Import Grocery using Lazy Loading / On Demand Loading ππ - lazy() function - A named import from react package
So when our App Home Page loads initially => It'll not load the code for Grocery. Only when I go to my Grocery page (Link) then only my grocery code should come up
lazy() - comes from our React Package - as a named import
-> Takes a CB Fn. which takes an import fn.
import React, { lazy } from "react";
// import Grocery from './components/Grocery'; // We'll not import Grocery using normal import
// Lazy load our Grocery Component
const Grocery = lazy(()=> import('./components/Grocery')); // Takes a CB Function which takes in import function that takes the path to the component
We'll lazy load our Grocery Component
When on Grocery page:
This above one line of code has a lot of power to it. Now we have a separate Bundle for Grocery Feature.
Both these bundles are now small and easy to load whenever required
Lazy-loading components with Suspense
When you're on the Home Page, your code of Grocery is not there.
When you go to Grocery page it might take few milliseconds to get the code for Grocery.
So until then it cannot load anything so we show a Placeholder for that time being - give that Placeholder (some JSX to show meanwhile we get code) inside Fallback.
import React, { lazy , Suspense} from "react";
{
path: '/grocery',
element: <Suspense fallback={<h1>Loading...</h1>}><Grocery/></Suspense>
// element: <Suspense fallback={<Shimmer/>}><Grocery/></Suspense>
},
It took 703ms in (Fast 3g) to load Grocery Code. So meanwhile we show a placeholder Loading... or Shimmer UI - Go to a Slower network (Slow/Fast 3G) to observe this.
If you do not wrap the Grocery (which has a different bundle) inside Suspense Component Wrapper. We'll encounter an error.
In the meantime the new bundle of code is loading React does not have anything to display so that's why we encounter this error.
These 4 lines of code can make your Large-scale Application very Fast, very Performant, lightweight & OPTIMIZED!!! β€οΈβπ₯
You should tell the interviewer that You can use Lazy Loading to distribute code into different chunks.
If your App is bloating or the App bundle size is increasing => So to reduce the bundle we do this Lazy Loading / Code Splitting... (for different features)
This will show that you're a Mature Senior Developer & can build Large Scale Applications!!
Stay Tuned for next Blogs!! β€οΈβπ₯
Subscribe to my newsletter
Read articles from Smit Desai directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by