In React, Hooks are very powerful and reusable, and if you need to use a common functionality in multiple components, then creating custom hooks is a best practice.
In this post, you’ll learn how to create and use custom hooks in React.
What is the Custom Hook?
Custom hook is a function that provides reusable logic by using React’s built-in hooks (useState
, useEffect
, etc.).
Its name starts with “use” (for example: useAuth
, useFetch
, etc.), so that React knows it’s a hook.
Custom hooks increase reusability and maintainability.
Steps for Creating Custom Hooks
Here’s the step-by-step guide for creating a custom counter hook:
Step 1: Create a Hook Function
function useCounter() {
}
export default useCounter;
Here,
This ensures React treats it as a hook and applies hook rules properly.
The function name starts with “use”, which is a convention for hooks.
Step 2: Add State and Functions
import { useState } from "react";
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
}
export default useCounter;
Here,
useState
is used to manage the count state.- increment, decrement, and reset functions modify the count state.
- This logic is reusable across multiple components.
Step 3: Return the values or functions that you need
import { useState } from "react";
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
export default useCounter;
Here,
- The custom hook now returns an object containing count and update functions.
- This allows any component to use these values and functions by destructuring.
Step 4: Use the custom hook in the counter component
import React from "react";
import useCounter from "./useCounter";
function CounterComponent() {
const { count, increment, decrement, reset } = useCounter(5);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
}
export default CounterComponent;
Here,
- The
useCounter
hook is imported and used inCounterComponent
. - The initial value 5 is passed as an argument.
- The count state and functions are extracted using object destructuring.
- Buttons are added to interact with the state using the hook’s functions.
By using this custom hook, you can reuse the same counter-logic in multiple components.
Why are Custom Hooks Useful?
Custom hooks are useful:
- Reusability increases.
- Code readability and maintainability improve.
- Reduces the need to write the same logic again and again.
Best Practice for Custom Hooks
- Always start the name of the custom hook with “use” (
useCounter
,useFetch
, etc.). - To manage state and side effects, use built-in hooks (
useState
,useEffect
, etc.). - Try to create hooks that are reusable and generic.
- If a custom hook is only needed in a single component, then writing that in a separate file is not required.
Real-World Examples of Custom Hooks
Here are some of the custom hooks that are useful in real-world applications.
useLocalStorage – For Storing Data
If you want to store data in local storage, then it’s a good practice to create a custom hook instead of writing localStorage.setItem
and localStorage.getItem
in each component.
import { useState, useEffect } from "react";
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const savedValue = localStorage.getItem(key);
return savedValue ? JSON.parse(savedValue) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
export default useLocalStorage;
Explanation:
- Retrieves and stores values in localStorage automatically.
- Uses
useState
to manage stored data. - Uses
useEffect
to update localStorage when the value changes.
Now, you can use this in any component.
import React from "react";
import useLocalStorage from "./useLocalStorage";
function ThemeComponent() {
const [theme, setTheme] = useLocalStorage("theme", "light");
return (
<div>
<h2>Current Theme: {theme}</h2>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle Theme
</button>
</div>
);
}
export default ThemeComponent;
Explanation:
- Uses
useLocalStorage
to store and retrieve the theme. - Clicking the button toggles between “light” and “dark” mode.
useFetch – To Make API Calls Easy
You can create a custom hook for handling API calls instead of writing fetch()
in each component.
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch data");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Explanation:
- Manages API calls with fetch inside
useEffect
. - Handles loading, data, and error states.
- Fetches data when the URL changes.
Now, you can use this in any component.
import React from "react";
import useFetch from "./useFetch";
function UsersComponent() {
const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/users");
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>Users List</h2>
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UsersComponent;
Explanation:
- Fetches and displays a list of users.
- Handles loading and error states for a smooth UI.
- Uses useFetch to keep API logic reusable.