In this blog, we will be further modularising our code by the use of utils/helper.js
file, building custom hooks, online/offline status detection feature. We'll also optimise our app and make it more performant using logical bundling/chunking.
utils/helper.js
All our custom hooks and helper file (that contains named exported functions that can be reused in the codebase) should be kept inside the utils
folder as shown below:
This makes our code more readable, modular and easily testable.
Custom Hooks
Reusing Logic with Custom Hooks: React comes with several built-in Hooks like useState
, useContext
, and useEffect
. Sometimes, you’ll wish that there was a Hook for some more specific purpose: for example, to fetch data, to keep track of whether the user is online, or to connect to a chat room. You might not find these Hooks in React, but you can create your own Hooks for your application’s needs.
According to the Single responsibility principle, there is no business of the API logic and the data fetching being in the component file, it should be outsourced into it's own file as a custom hook which needs to be imported and called whenever we need data. This data can be used while rendering the component. The component file should only be concerned with the rendering part.
// RestaurantMenu.jsx
import { useParams } from "react-router-dom";
import Shimmer from "./cards/Shimmer";
import { IMG_CDN_URL } from "../config";
import useRestaurantInfo from "../utils/useRestaurantInfo";
const RestaurantMenu = () => {
const { resId } = useParams();
const [restaurant, menu] = useRestaurantInfo(resId);
return !restaurant ? (
<Shimmer />
) : (
<div className="restaurant-menu-container">
{/* More JSX... */}
</div>
);
};
export default RestaurantMenu;
// useRestaurantInfo.js
import { useEffect, useState } from "react";
import { FETCH_MENU_URL } from "../config";
const useRestaurantInfo = (resId) => {
const [restaurant, setRestaurant] = useState(null);
const [menu, setMenu] = useState([]);
useEffect(() => {
getRestaurantInfo();
}, []);
async function getRestaurantInfo() {
// API call logic after which we set the
// restaurant and menu states using
// setRestaurant and setMenu respectively
}
return [restaurant, menu];
};
export default useRestaurantInfo;
Online/Offline status detector feature
Imagine you’re developing an app that heavily relies on the network (as most apps do). You want to warn the user if their network connection has accidentally gone off while they were using your app. How would you go about it? It seems like you’ll need two things in your component:
A piece of state that tracks whether the network is online.
An Effect that subscribes to the global
online
andoffline
events, and updates that state.
This will keep your component synchronised with the network status.
// useOnline.js
import { useEffect, useState } from "react";
const useOnline = () => {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const handleOnline = () => {
setIsOnline(true);
};
const handleOffline = () => {
setIsOnline(false);
};
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
return isOnline;
};
export default useOnline;
// Body.jsx
import { useState } from "react";
import { Link } from "react-router-dom";
import RestaurantCard from "./cards/RestaurantCard";
import Shimmer from "./cards/Shimmer";
import { filterData } from "../utils/helper";
import useRestaurantCards from "../utils/useRestaurantCards";
import useOnline from "../utils/useOnline";
const Body = () => {
const [searchInput, setSearchInput] = useState("");
const [restaurants, defaultRestaurants] = useRestaurantCards();
const online = useOnline();
if (!online) {
return <h1>🔴 Offline, Please check your internet connection!!!</h1>;
}
// Early return
if (!defaultRestaurants) return null;
return defaultRestaurants.length === 0 ? (
<Shimmer />
) : (
// Some JSX
);
};
export default Body;
To test this, let me simulate going offline using the network tab and selecting the offline
option as shown:
Now, if change it back to the No throttling
option then we'll be back online:
Chunking
Most React apps will have their files “bundled” using tools like Parcel, Webpack, Rollup or Browserify. Bundling is the process of following imported files and merging them into a single file: a “bundle”. This bundle can then be included on a webpage to load an entire app at once.
Now, think of a large production ready app like MakeMyTrip which has many features and booking services that it offers: flights, trains, buses, hotels and much more. Majority users visit MakeMyTrip to book flights hence it opens the flight booking tab by default. Flight booking tab in and of itself has a lot of components inside it, and so do the train, bus, hotel tabs. With so many components in a large scale production level app, if the bundler bundled all the components into a single javascript file, then the bundle size would be huge! And so would be the loading time. So, to make the app performant, we use a concept called as chunking. It goes by many names:
chunking
code splitting
dynamic bundling
on demand loading
lazy loading
dynamic import
Bundling is great, but as your app grows, your bundle will grow too. Especially if you are including large third-party libraries. You need to keep an eye on the code you are including in your bundle so that you don’t accidentally make it so large that your app takes a long time to load.
To avoid winding up with a large bundle, it’s good to get ahead of the problem and start “splitting” your bundle. Code-Splitting is a feature supported by many bundlers which can create multiple bundles that can be dynamically loaded at runtime.
Code-splitting or chunking your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you haven’t reduced the overall amount of code in your app, you’ve avoided loading code that the user may never need, and reduced the amount of code needed during the initial load.
lazy(load)
lazy
lets you defer loading component’s code until it is rendered for the first time.
const SomeComponent = lazy(load)
Call lazy
outside your components to declare a lazy-loaded React component:
import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
Parameters
load
: A function that returns a Promise or another thenable (a Promise-like object with athen
method). React will not callload
until the first time you attempt to render the returned component. After React first callsload
, it will wait for it to resolve, and then render the resolved value’s.default
as a React component. Both the returned Promise and the Promise’s resolved value will be cached, so React will not callload
more than once. If the Promise rejects, React willthrow
the rejection reason for the nearest Error Boundary to handle.
Returns
lazy
returns a React component you can render in your tree. While the code for the lazy component is still loading, attempting to render it will suspend. Use <Suspense>
to display a loading indicator while it’s loading.
What is suspense
?
<Suspense>
lets you display a fallback until its children have finished loading.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
Adding Instamart into our Food house app
Let's first implement a dummy component for Instamart:
// Instamart.jsx
const Instamart = () => {
return (
<div>
<h1>Instamart</h1>
<h2>100s of componenets inside it...</h2>
</div>
);
};
export default Instamart;
// App.js
const Instamart = lazy(() => import("./components/Instamart"));
// we'll add it to our router
const appRouter = createBrowserRouter([
{
path: "/",
element: <AppLayout />,
errorElement: <Error />,
children: [
// Other paths...
{
path: "/instamart",
element: (
<Suspense fallback={<Shimmer />}>
<Instamart />
</Suspense>
),
},
],
},
,
]);
Now, let's load our homepage with the js files filter on the network tab, you'll only see a single js file:
Let's set the internet speed option to Slow 3G
so that we can observe the Shimmer effect properly. Once we click on the Instamart tab on the header, we see:
After some time, we can see our Instamart component properly loaded. Here, you'll see two js files loaded, one previously loaded for the homepage and another for our now loaded Instamart component:
That's all for now folks. See you in the next blog!