Creating Custom Hooks

Learn how to create custom hooks in React to simplify logic, improve reusability, and keep your components clean.

Loading...
Creating Custom Hooks

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 in CounterComponent.
  • 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.