Building a Custom Hook in React for Reusable Logic

Building a Custom Hook in React for Reusable Logic

React provides a powerful tool called custom hooks that allows developers to encapsulate reusable logic within a function. By leveraging custom hooks, you can write cleaner and more maintainable code, reducing redundancy and improving the overall structure of your application.

In this article, we’ll dive into what custom hooks are, why they’re beneficial, and how to build one for reusable logic in your React projects.


What Are Custom Hooks?

Custom hooks are JavaScript functions that start with the word use, following React's hook naming convention. They allow you to extract reusable logic from components into standalone functions, making your code modular and easier to maintain.

For example, if multiple components in your app require the same logic (like fetching data, managing forms, or handling authentication), you can create a custom hook to encapsulate that logic.


Why Use Custom Hooks?

  1. Code Reusability:
    Custom hooks allow you to reuse the same logic across multiple components, reducing duplication.

  2. Cleaner Components:
    By moving complex logic into hooks, your components can focus solely on rendering UI.

  3. Easier Testing:
    Hooks can be tested independently of components, making them easier to debug and validate.

  4. Separation of Concerns:
    Custom hooks separate business logic from UI logic, improving code organization.


Building Your First Custom Hook

Let’s build a custom hook called useFetch for fetching data from an API. This hook will handle the logic of making a network request, managing loading and error states, and returning the fetched data.

Step 1: Create the Custom Hook

Create a new file named useFetch.js in your project.

import { useState, useEffect } from "react";

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

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;

Step 2: Use the Hook in a Component

Now, use the useFetch hook in a React component to fetch and display data.

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

const Posts = () => {
  const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/posts");

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {data.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default Posts;

Best Practices for Custom Hooks

  1. Keep It Focused:
    Each custom hook should focus on a single piece of reusable logic.

  2. Use the use Prefix:
    Always start the name of your custom hook with use to follow React's convention and ensure React correctly identifies it as a hook.

  3. Encapsulate Only Logic:
    Avoid adding rendering logic or UI elements in custom hooks.

  4. Make It Configurable:
    Accept parameters to make your custom hooks more flexible and adaptable.


More Examples of Custom Hooks

1. A Hook for Managing Form State

import { useState } from "react";

const useForm = (initialValues) => {
  const [values, setValues] = useState(initialValues);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues({
      ...values,
      [name]: value,
    });
  };

  return { values, handleChange };
};

export default useForm;

Usage Example:

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

const LoginForm = () => {
  const { values, handleChange } = useForm({ email: "", password: "" });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(values);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" value={values.email} onChange={handleChange} placeholder="Email" />
      <input name="password" value={values.password} onChange={handleChange} placeholder="Password" />
      <button type="submit">Login</button>
    </form>
  );
};

2. A Hook for Managing Dark Mode

import { useState, useEffect } from "react";

const useDarkMode = () => {
  const [isDarkMode, setIsDarkMode] = useState(() => {
    return localStorage.getItem("darkMode") === "true";
  });

  useEffect(() => {
    localStorage.setItem("darkMode", isDarkMode);
    document.body.className = isDarkMode ? "dark-mode" : "";
  }, [isDarkMode]);

  return [isDarkMode, setIsDarkMode];
};

export default useDarkMode;

Usage Example:

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

const ThemeSwitcher = () => {
  const [isDarkMode, setIsDarkMode] = useDarkMode();

  return (
    <button onClick={() => setIsDarkMode((prev) => !prev)}>
      Switch to {isDarkMode ? "Light" : "Dark"} Mode
    </button>
  );
};

Advantages of Using Custom Hooks

  • Simplified Components: Components focus purely on UI while logic resides in hooks.

  • Reusability: Write logic once and reuse it across multiple components.

  • Testability: Custom hooks can be tested independently, ensuring robust logic.

  • Maintainability: Encapsulated logic is easier to update and debug.


Conclusion

Custom hooks are a game-changer in React development, enabling you to extract, reuse, and manage logic efficiently. Whether you're fetching data, handling forms, or managing themes, custom hooks allow you to write cleaner, more organized code.

Start building custom hooks for your projects today, and experience the benefits of modular and reusable logic! 🚀