DEV Community

DCT Technology Pvt. Ltd.
DCT Technology Pvt. Ltd.

Posted on

Building a Custom Hook in React: When and How to Use It

Are you still duplicating logic across multiple components in your React app? That repetitive API fetch, the same input field handler, or scroll event listener you're copying over and over… it’s time to level up.

Let’s break out of that loop and learn how to build a custom logic extractor that makes your code cleaner, more maintainable, and scalable.

By the end of this post, you'll know exactly:

  • Why this reusable technique is a game-changer for React apps.
  • When it’s the right time to extract shared logic.
  • How to create a powerful utility with real-world examples.
  • How to supercharge it with open-source tools and community best practices.

Image description

💡 Why Bother Creating Reusable Logic?

Imagine you're working on a dashboard. You have multiple components that:

  • Fetch data from APIs.
  • Show loading and error states.
  • Rerun the fetch on a refresh button.

You’re probably repeating something like this:

useEffect(() => {
  setLoading(true);
  fetch('/api/data')
    .then(res => res.json())
    .then(setData)
    .catch(setError)
    .finally(() => setLoading(false));
}, []);
Enter fullscreen mode Exit fullscreen mode

Not once. Not twice. But in multiple places.

It works. But what happens when you need to:

  • Add a caching layer?
  • Retry on failure?
  • Cancel the fetch when the component unmounts?

That's when reusability becomes essential. It keeps your logic in one place and lets you test, extend, and maintain it more easily.


🚀 Let’s Create a Reusable API Fetch Logic

Let’s walk through building a reusable function for data fetching with loading and error state handling:

import { useState, useEffect } from 'react';

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

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

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

    fetchData();

    return () => {
      isMounted = false; // prevents state updates on unmounted components
    };
  }, [url]);

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

Now you can use it like this:

const { data, loading, error } = useFetchData('/api/data');
Enter fullscreen mode Exit fullscreen mode

🧠 When Should You Create One?

Don’t extract everything too early. Here’s a simple rule:

If you repeat the same logic more than twice, consider abstracting it.

Use these checkpoints:

  • Is the logic repeated across 2 or more components?
  • Does the logic involve side effects like fetching, event listeners, or timers?
  • Would moving it to one place improve readability and testability?

🛠️ Add Superpowers with Tools

Want to make it even better?

  1. Add caching and auto-revalidation with SWR or React Query. They handle many things for you.
  2. Use TypeScript for better type inference and safety.
  3. Track performance by wrapping with React Profiler.
  4. Memoize to avoid unnecessary renders.

⚙️ Real-World Ideas You Can Reuse

Here are a few real-life reusable patterns you can create:

  • Form input state management
  const useInput = (initialValue = '') => {
    const [value, setValue] = useState(initialValue);
    const onChange = (e) => setValue(e.target.value);
    return { value, onChange };
  };
Enter fullscreen mode Exit fullscreen mode
  • Scroll position tracker Useful for showing “Back to Top” buttons or lazy loading sections.
  const useScrollPosition = () => {
    const [position, setPosition] = useState(window.scrollY);

    useEffect(() => {
      const handleScroll = () => setPosition(window.scrollY);
      window.addEventListener('scroll', handleScroll);
      return () => window.removeEventListener('scroll', handleScroll);
    }, []);

    return position;
  };
Enter fullscreen mode Exit fullscreen mode

Want more reusable logic like this? Drop a comment and let me know what you’re building! 💬


📌 Pro Tips to Keep in Mind

  • Always keep logic decoupled from UI.
  • Don't create these unless you're repeating logic more than once.
  • Keep the API clean and return only what's needed.
  • Document it well so your team knows how to use it.

🔁 Turn Logic Into Lego Blocks

Think of this method as building your own Lego set. Once you’ve built one block, you can plug it in anywhere in your app.

It’s a mindset shift from “How do I get this done?” to “How can I make this future-proof and clean?”

If you found this helpful, don’t forget to:

✅ Share this post with your dev circle
✅ Comment what you'd like to modularize next
✅ **Follow [DCT Technology]for more reusable web dev patterns and UI/UX tips


#ReactJS #WebDevelopment #JavaScript #CleanCode #Frontend #ReactHooks #CodeQuality #DeveloperTips #DCTTechnology #UIDesign #Programming #ReusableComponents #CustomHooks #DevCommunity

Top comments (0)