Creating Custom Hooks in React

ReactCustomHook

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 (useStateuseEffect, etc.).

Its name starts with “use” (for example: useAuthuseFetch, 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 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” (useCounteruseFetch, etc.).
  • To manage state and side effects, use built-in hooks (useStateuseEffect, 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.

Leave a Reply

Your email address will not be published. Required fields are marked *