DEV Community

Muhammad Muneeb Ur Rehman
Muhammad Muneeb Ur Rehman

Posted on

Demystifying Custom Hooks in React: What, When, and How

React hooks have completely changed the way we write components. They allow us to handle state, side effects, and context in a cleaner, more functional way. But sometimes, we find ourselves writing the same hook logic again and again. That’s where custom hooks come to the rescue.

In this article, we’ll explore:

  • What custom hooks are
  • When to use them
  • How to create and use them with real-world examples

What are Custom Hooks?

A custom hook is simply a JavaScript function whose name starts with use and that can use other hooks inside it (useState, useEffect, useContext, etc.).

They allow us to:

  • Extract reusable logic from components
  • Keep your components clean and focused
  • Share logic between different components without duplicating code

Think of them as small utilities that encapsulate hook-related logic and can be plugged into any component.

When to Use Custom Hooks

You should consider creating a custom hook when:

Reusing logic:

If multiple components need the same piece of logic (e.g., fetching data, handling form inputs).

Improving readability:

Components should mainly focus on UI, not business logic. Moving logic to hooks makes code more readable.

Separation of concerns

Keeps logic modular and testable, just like splitting functions in regular JavaScript.

How to Create a Custom Hook

Let’s start with a real-world scenario: fetching data from an API.
Normally, we’d write the fetching logic inside a component with useEffect. But if multiple components need to fetch data, we’ll end up repeating the same code.

Example 1: A Custom Hook for Fetching Data

import { useState, useEffect } from "react";

// Custom Hook
export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;

    async function fetchData() {
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error("Failed to fetch");
        const result = await response.json();
        if (isMounted) {
          setData(result);
          setError(null);
        }
      } catch (err) {
        if (isMounted) setError(err.message);
      } finally {
        if (isMounted) setLoading(false);
      }
    }

    fetchData();

    return () => { isMounted = false; };
  }, [url]);

  return { data, loading, error };
}
Enter fullscreen mode Exit fullscreen mode

Using it in a Component

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

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

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now, whenever we need to fetch data in another component, we just call useFetch(url) instead of rewriting the same code.

Example 2: A Custom Hook for Form Handling

Forms are everywhere, and handling form state can get repetitive. Let’s create a reusable hook to manage form inputs.

import { useState } from "react";

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

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

  const resetForm = () => setValues(initialValues);

  return { values, handleChange, resetForm };
}
Enter fullscreen mode Exit fullscreen mode

Using it in a Login Form

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

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("Submitted:", values);
    resetForm();
  };

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

Any new form (signup, profile update, contact form) can now reuse this same useForm hook.

Key Takeaways

  • Custom hooks let you encapsulate logic and reuse it across components.

  • They make your code cleaner, modular, and easier to maintain.

  • Use them for tasks like:

    • Fetching data
    • Form handling
    • Authentication checks
    • Managing browser events (scroll, resize, etc.)

Ending Notes

Custom hooks are not some advanced React trick they’re just functions. But when used wisely, they can dramatically improve your code quality and development speed.

Next time you find yourself copy-pasting hook logic, stop and think:
“Can I move this into a custom hook?”

Chances are, the answer is yes and your future self will thank you.

Top comments (0)