DEV Community

Cover image for How to Build a Custom Hook in React JS
Ghulam Murtaza for epicX

Posted on โ€ข Edited on

How to Build a Custom Hook in React JS

๐Ÿš€ Introduction

React has revolutionized frontend development with its component-based architecture. One of its most powerful features is custom hooks, which allow developers to encapsulate and reuse logic efficiently. If you find yourself using useState, useEffect, or useRef repeatedly across multiple components, creating a custom hook can significantly improve code organization and reusability.

๐Ÿ“Œ In this article, weโ€™ll cover:

  • ๐ŸŽฏ What custom hooks are and why theyโ€™re useful
  • ๐Ÿ› ๏ธ A step-by-step guide to creating a custom hook
  • ๐Ÿ“Œ Real-world examples of practical custom hooks
  • โœ… Best practices for writing custom hooks

๐Ÿค” What is a Custom Hook in React?

A custom hook is a JavaScript function that starts with use (e.g., useCustomHook) and encapsulates reusable stateful logic using Reactโ€™s built-in hooks. Instead of repeating logic in multiple components, we can extract it into a custom hook and use it across our application.

๐Ÿ’ก Example Use Case: Suppose you need to interact with localStorage in multiple components. Instead of writing localStorage.getItem() and localStorage.setItem() repeatedly, you can create a custom hook to handle this logic.


๐Ÿ—๏ธ How to Create a Custom Hook

To create a custom hook, follow these steps:

  • ๐Ÿท๏ธ Define a function with a name starting with use.
  • ๐Ÿ”„ Use built-in hooks like useState, useEffect, or useContext inside the function.
  • ๐Ÿ“ค Return values or functions that can be used by components.
  • ๐Ÿ“Œ Import and use the custom hook in your components.

Letโ€™s now build some practical custom hooks that solve real-world problems.


Example 1: ๐Ÿ—„๏ธ useLocalStorage (Managing Local Storage in React)

Local storage is commonly used to persist user preferences or session data. Letโ€™s create a custom hook for handling local storage.

Step 1: ๐Ÿ› ๏ธ Create the Hook (useLocalStorage.js)

import { useState, useEffect } from "react";

function useLocalStorage(key: string, initialValue: any) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error("Error accessing localStorage", error);
      return initialValue;
    }
  });

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      console.error("Error saving to localStorage", error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue];
}

export default useLocalStorage;
Enter fullscreen mode Exit fullscreen mode

Step 2: ๐ŸŽฏ Use It in a Component

import React from "react";
import useLocalStorage from "./useLocalStorage";

function App() {
  const [name, setName] = useLocalStorage("username", "Guest");

  return (
    <div>
      <h2>Welcome, {name}! ๐ŸŽ‰</h2>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

โœ… Why This is Useful:

  • ๐Ÿ’พ Saves user input persistently in local storage.
  • ๐Ÿ”„ Retains values even after page refresh.

Example 2: ๐ŸŒ useFetch (Handling API Requests in React)

Fetching data is a common operation in React applications. Instead of writing fetch() calls repeatedly, we can create a reusable hook for API requests.

Step 1: ๐Ÿ› ๏ธ Create the Hook (useFetch.js)

import { useState, useEffect } from "react";

function useFetch(url: string) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error("Failed to fetch");
        return res.json();
      })
      .then((data) => setData(data))
      .catch((error) => setError(error))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

export default useFetch;
Enter fullscreen mode Exit fullscreen mode

Step 2: ๐ŸŽฏ Use It in a Component

import React from "react";
import useFetch from "./useFetch";

function UsersList() {
  const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/users");

  if (loading) return <p>โณ Loading...</p>;
  if (error) return <p>โŒ Error: {error.message}</p>;

  return (
    <ul>
      {data.map((user: any) => (
        <li key={user.id}>๐Ÿ‘ค {user.name}</li>
      ))}
    </ul>
  );
}

export default UsersList;
Enter fullscreen mode Exit fullscreen mode

โœ… Why This is Useful:

  • ๐Ÿ”„ Centralizes API fetching logic.
  • โšก Handles loading and error states efficiently.

๐Ÿ† Best Practices for Writing Custom Hooks

  • ๐ŸŽฏ Keep Hooks Focused: Each hook should have a single responsibility.
  • ๐Ÿท๏ธ Follow the Naming Convention: Always start function names with use.
  • ๐Ÿ”„ Use Dependency Arrays Wisely: Avoid unnecessary re-renders in useEffect.
  • ๐Ÿ“Œ Ensure Hooks Are Reusable: Avoid using component-specific logic inside hooks.
  • ๐Ÿงช Test Custom Hooks: Use Jest and React Testing Library to ensure they work correctly.

๐ŸŽ‰ Conclusion

Custom hooks in React allow developers to abstract logic, improve code reusability, and enhance maintainability. Whether it's handling local storage, API calls, or dark mode toggling, custom hooks provide an elegant solution to avoid redundant code.

By following best practices and structuring hooks properly, you can build efficient and reusable components that scale well in any React project.

Would you like to explore more complex custom hooks? Let me know in the comments! ๐Ÿš€

Image of Timescale

๐Ÿš€ pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applicationsโ€”without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post โ†’

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more