Creating Custom Hooks
Learn how to create custom hooks in React to simplify logic, improve reusability, and keep your components clean.
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.
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,
- The function name starts with “use”, which is a convention for hooks.
- This ensures React treats it as a hook and applies hook rules properly.
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.