DEV Community

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

Posted 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! πŸš€

Top comments (0)