Back to Blog
Lesson 32 of the React Fundamentals: Build Modern UIs from Scratch course
ReactJune 25, 20263 min read

Extracting Custom Hooks: A Guide to React Reusability

Learn to create custom hooks in React to abstract complex data-fetching logic. Improve your code reusability and simplify your components by building a useFetch.

reacthookscustom-hooksrefactoringjavascriptweb-developmentfrontend

Previously in this course, we explored Folder Structure Best Practices to organize our project. Now that our movie-browser app is growing, you've likely noticed that our data-fetching logic—the useState and useEffect patterns we mastered in Handling Loading States in React and Managing Errors—is starting to clutter our components.

It’s time to solve this by creating custom hooks.

The Problem: Logic Bloat

When you fetch data in multiple components, you find yourself rewriting the same boilerplate: declaring data, loading, and error states, plus the useEffect block to trigger the fetch.

Not only does this lead to repetitive code, but it also makes your components harder to read. If you decide to change how you handle errors or add a loading timeout, you’d have to hunt down every instance of that logic throughout your app. Abstraction allows us to pull this shared behavior into a single, reusable function.

From First Principles: What is a Custom Hook?

A custom hook is simply a JavaScript function that starts with the word use and calls other React hooks.

Think of it as a "logic container." It doesn't return JSX; instead, it returns the values or functions that your component needs to render its UI. By moving our data-fetching logic into a custom hook, we treat that logic as a black box: the component asks for data, and the hook provides it.

Worked Example: Creating useFetch

Let’s move our movie-fetching logic into a dedicated file. Create a new file at src/hooks/useFetch.js.

JAVASCRIPT
import { useState, useEffect } from CE9178">'react';

export function 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(CE9178">'Failed to fetch');
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

Now, look at how much cleaner your main component becomes. Instead of ten lines of state and effects, you have one:

JAVASCRIPT
import { useFetch } from CE9178">'./hooks/useFetch';

function MovieList() {
  const { data, loading, error } = useFetch(CE9178">'https://api.example.com/movies');

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

  return <ul>{data.map(movie => <li key={movie.id}>{movie.title}</li>)}</ul>;
}

Hands-on Exercise

Refactor your existing movie-browser search logic.

  1. Create a useFetch hook that accepts a url.
  2. Import useFetch into your App.jsx.
  3. Replace your manual useEffect and useState calls in App.jsx with the return values from your new hook.
  4. Ensure the UI still renders correctly.

Common Pitfalls

  • Forgetting the use prefix: React relies on the use naming convention to identify hooks. If you name your function fetchData instead of useFetch, React will throw an error when you try to call other hooks inside it.
  • Over-abstracting: Don't turn every single line of logic into a hook. Only extract code that you actually reuse in at least two places, or logic that is so complex it obscures the purpose of your component.
  • Missing Dependencies: Just like in a standard useEffect, if your custom hook uses variables inside its effect, make sure those variables are included in the dependency array (or passed as arguments to the hook).

Recap

Custom hooks are the primary tool for reusability in modern React. By extracting stateful logic, you keep your components focused on the UI, while your hooks handle the "how" of data management. This separation of concerns makes your codebase significantly easier to test and maintain.

Up next: We'll tackle Prop Drilling and Context API to share data across our component tree without passing props through every single level.

Similar Posts