Posted on: Jul 13 '23
Read time: 4min
React custom hooks for everyone🪝
Maintaining react components that contain too much logic has never been easy, thanks to react custom hook, we can distribute our component's logic into a reusable function that can be imported into any component of our application. In case you are new to React, custom hooks are not special functions provided by React, instead, they are built on top of existing hooks.
In this project, we shall build an application to fetch a location based on the provided IP address, this project will be a favorable scenario for learning custom hooks since we practice both reusability and HTTP request within custom hooks.
Create application
In this project we shall be using Vite, if you are new to Vite don't worry everything you know about React remains the same. Run the command below for building your application, and for more information on Vite use the following link
npm create vite@latest myproject -- --template react-t
cd myproject
npm install
//use the code editor of your choice
After creating the application, get rid of the App.css or delete all code inside of it, we don't need it in this project, all the styling will be written in the index.css file. To see the custom hooks in action we shall start without them and then change our logic to custom hook later.
css
* {
margin: 0px;
padding: 0px;
}
.container {
max-width: 600px;
min-height: 300px;
margin: auto;
margin-top: 40px;
border: 0.5px solid rgba(0, 0, 0, 0.127);
padding: 20px;
}
.container p
{
margin: 20px 10px;
}
.button-container {
max-width: 600px;
margin: auto;
margin-top: 20px;
margin-bottom: 50px;
}
.button-container button {
width: 100%;
padding: 15px;
}
import { useState } from 'react'
function App() {
const [loading, setLoading] = useState<boolean>(false);
const [location, setLocation] = useState<{} | any>();
const [errorMessage, setErrorMessage] = useState<string>();
const getLocation = async () => {
try {
setLoading(true);
const request = await fetch("http://ip-api.com/json/"); //provide an ip address as request param if you don't want to use your current ip address e.g:http://ip-api.com/json/24.48.0.1"
const response = await request.json();
setLocation(response);
setLoading(false)
} catch (error: any) {
//do what you want with the error
setLoading(false)
setErrorMessage(error.message);
}
};
return (
<>
<div className="container">
<div className="location-info">
// render location detail
{location &&
Object.keys(location).map((key) => {
return <p><strong>{key}</strong>: {location[key]}</p>
})
}
{
loading && <p>...loading</p>
}
{
errorMessage && <p>{errorMessage}</p>
}
</div>
</div>
<div className="button-container">
<button onClick={() => getLocation()}>Get location</button>
</div>
</>
)
}
export default App
As you can the application is working perfectly fine, you click on the button you get the location, and everyone is happy 😎, but what if you want to do the same thing in other components of your application, do you imagine how many duplicated code you will end up with 🤔, don't worry let's solve that problem with a custom hook.
In this second example, I'm going to create a useGeoLocation
hook that will provide all the states that we need, including the location.
useLocationHook
import { useState } from "react"
export const useGeoLocation = (): { location: any, errorMessage: string, loading: boolean, getLocation: (id?: string) => void } => {
const [loading, setLoading] = useState<boolean>(false);
const [location, setLocation] = useState<any>(); //i use any type for this state because i'm not sure about what will be returned;
const [errorMessage, setErrorMessage] = useState<string>("");
const getLocation = async (ip?: string) => {
try {
setLoading(true);
const request = await fetch("http://ip-api.com/json/" + (ip ? ip : "")); //provide an ip address as request param if you don't want to use your current ip address e.g:http://ip-api.com/json/24.48.0.1"
const response = await request.json();
setLocation(response);
setLoading(false)
} catch (error: any) {
setLoading(false)
setErrorMessage(error.message);
}
}
return {
loading,
location,
errorMessage,
getLocation
}
}
useLocation Hook in action
import { useGeoLocation } from './hooks/useGeoLocation';
function App() {
const { loading, location, getLocation, errorMessage } = useGeoLocation();
return (
<>
<div className="container">
<div className="location-info">
{location &&
Object.keys(location).map((key) => {
return <p><strong>{key}</strong>: {location[key]}</p>
})
}
{
loading && <p>...loading</p>
}
{
errorMessage && <p>{errorMessage}</p>
}
</div>
</div>
<div className="button-container">
<button onClick={() => getLocation()}>Get location</button>
</div>
</>
)
}
export default App
Now you have a portable function that can be used anywhere in your application, you can go a bit deep and use useReducer
instead dozen of use states, but since this article is about custom hooks, let's focus on that only.
You may think that states available in the useGeoLocation
hook are shared among all components that call it, but that is not the case, each component that calls a custom hook will have its own separate instance of the hook's state, this allows you to use thing like useEffect
or useLayoutEffect
inside your custom hooks.
Conclusion
Custom hooks 🪝 can improve the quality of your code and help you with reusable code 😊, use them as much as possible clean code always improves the productivity of your team.
If you find this article useful comment, or why not subscribe🤔. Thanks for reading.