William Imoh
6 min readJan 03 2021
Manage Functionalities in Large Apps using Custom React Hooks
Hi,
Since the introduction of React hooks, the creation and utilization of functional components became even more seamless. With useEffect
and useState
lifecycle methods previously available to class components are also available in functional components.
React's very purpose is to provide reusable blocks of code that form the various parts of an application. In this post, we'll explore how to use custom hooks to abstract component functionality reusable across a React application.
To follow through with this post, you should be conversant with React.js.
Why use custom hooks
You may wonder why you should even bother with writing custom React hooks when we can write your state and effect logic in the component and get to building the UI.
You are right.
It would be best if you mostly used a custom hook when you need to abstract a frequently used component functionality utilizing state and effect logic. Custom hooks are primarily present in large applications with multiple repetitive portions.
For example, in a b2b e-commerce marketplace application, you may require fetching order data or seller information in multiple components. You can handle this particular fetching operation every time a component requires the data, or you can make a hook to handle it. The same applies to fetching location data in an application where user location is required in multiple components. Here are some reasons why I use custom hooks in large projects:
- Provides useful abstraction as the same hook can be used across multiple components
- Side effects like utility function calls, application state update, and singular hook requirements are managed independently with cleanups
- You can use multiple hooks in one component without clutter.
- In Typescript, you want to have all the types in one place also, and not bloat the component code for readability
If you don't have to handle this reusability, excuse my firm opinion, avoid the hasty abstraction, and don't use custom hooks.
Structure of custom hooks
Custom hooks are simply functions that encapsulate React useEffect
and useState
APIs. They take parameters as specified and return data. The data could be an array, object, and primitive data types as specified.
Within the hook, all the magic happens. This hook is used across components. The result is a cleaner and well-organized codebase.
Here's what a custom hook looks like that fetches data for an order, showing the various parts in comments:
import { useEffect, useState } from "react"; // hook definition function useGetOrder(input) { const { id } = input; // component state creation const [orderId, setOrderId] = useState(id); const [isLoading, setIsLoading] = useState(false); const [hookData, setHookData] = useState(undefined); // Function to run on first load useEffect(() => { setIsLoading(true); // fetch data const fetchData = async () => { let orderData; try { orderData = await getOrder(orderId); } catch (e) { throw Error(e); } setHookData(orderData); setIsLoading(false); }; fetchData(); // handle cleanup return async () => { await unMountFn(); }; }, [orderId]); // hooks return array return [{ isLoading, hookData }, setOrderId]; } // export hooks export { useGetOrder };
From the snippet above, we can see the hook has the following parts:
- Module import (useState & useEffect)
- Function arguments restructuring
- State creation
- Component mount logic in
useEffect
- Component unmount logic (returned in
useEffect
) - Component update variable
- Hooks return data
- Hooks export
This hook depicts a data fetching operation on receipt/update of an input variable orderId
.
Instead of fetching data in useEffect
, you could use a web API to transform data, and you could store data in the application state (if it's a valid use case) or call a utility function.
Custom hooks in action
Below is the hook we shared earlier to fetch an order data in use. With a familiar file name of useGetOrder.js
, we have the following content:
import { useEffect, useState } from "react"; // API call to get data async function getOrder(id) { const res = await fetch("./order.json"); const data = await res.json(); return data; } // unmount Function async function unMountFn(data) { // handle any cleanup process } // hook definition function useGetOrder(input) { const { id } = input; // component state creation const [orderId, setOrderId] = useState(id); const [isLoading, setIsLoading] = useState(false); const [hookData, setHookData] = useState(undefined); // Function to run on first load useEffect(() => { setIsLoading(true); // fetch data const fetchData = async () => { let orderData; try { orderData = await getOrder(orderId); } catch (e) { throw Error(e); } setHookData(orderData); setIsLoading(false); }; fetchData(); // handle cleanup return async () => { await unMountFn(); }; }, [orderId]); // hooks return array return [{ isLoading, hookData }, setOrderId]; } // export hooks export { useGetOrder };
In the hook, we created functions to fetch data from a local json file, a function to be called on component destruction, and the hook's definition.
The hook function takes an input, and in the hook definition, we create state variables to hold the input data, loading state, and hooks data.
NB: The input data in this function is for reference and not utilized in the hooks logic
The hook returns an array containing an object in the first index to retrieve the loading state and the hook data. setOrderId
, which modifies the input data, is assigned the second index.
This hook is used in a component to fetch order data like this:
import React from "react"; import { useGetOrder } from "../hooks/useGetOrder"; const HomeOrder = () => { const [{ isLoading, hookData }, setOrderID] = useGetOrder(123); return ( <div> <h3>Home Order</h3> {isLoading && <p>Fetching order ⏳</p>} {hookData && ( <div> <p>ID: {hookData.id}</p> <p>Payment Captured: {hookData.paymentCaptured ? "True" : "False"}</p> <p>Amount: ${hookData.totalAmount}</p> <p>Shipping Fee: ${hookData.shippingFee}</p> <p>Shipping Address: {hookData.shippingAddress}</p> <p>User ID: {hookData.userId}</p> <h4>Order Items</h4> {hookData.products.map((product, key) => ( <div key={key}> <p> {product.title} - ${product.price} </p> </div> ))} </div> )} </div> ); }; export { HomeOrder };
The data, once fetched, can be used in the component. Rather than have the full state and mount logic in the component, we now have it as a hook that can be used by multiple components.
NB: Just as you cannot use native React hooks outside of a React component, custom hooks containing useState
and useEffect
also cannot be used outside a react component
Here's the final Codesandbox with the demo.
For large projects, you could do several optimizations and customizations to improve user experience and flexibility. These include:
- Having a wrapper for custom hooks with Types and generic configurations.
- Abstracting mount, unmount, error, and loading functions as parameters in the hooks definition.
Summary
In this post, we saw how to create a custom hook to handle reusable component logic in a React app. We also learned why we use custom hooks and how custom hooks look.
To a better 2021, and happy new year!
William.
About the author
I love solving problems, which has led me from core engineering to developer advocacy and product management. This is me sharing everything I know.
More articles
Akshat Virmani
6 min readAug 24 2024
How to add GitHub Copilot in VS Code
Learn how to add GitHub Copilot to Visual Studio Code for AI-assisted coding. Boost productivity, reduce errors, and get intelligent code suggestions in seconds.
Read Blog
Akshat Virmani
6 min readAug 09 2024
Common API Integration Challenges and How to Overcome Them
Discover common API integration challenges and practical solutions. Learn how to optimize testing, debugging, and security to streamline your API processes efficiently.
Read Blog
Akshat Virmani
6 min readJun 20 2024
Web Scraping using Node.js and Puppeteer
Step-by-step tutorial on using Node.js and Puppeteer to scrape web data, including setup, code examples, and best practices.
Read Blog