Back to Blog
Lesson 5 of the Intermediate React: Hooks, State & Data Patterns course
ReactJune 25, 20264 min read

Introduction to Custom Hooks: Master Abstraction in React

Learn how to use custom hooks to achieve code reuse and clean code in React. Discover how to identify extractable logic and follow the Rules of Hooks.

ReactHooksCustom HooksClean CodeAbstractionJavaScriptfrontend

Previously in this course, we explored how to optimize our components by managing function stability with useCallback and preventing unnecessary re-renders using useMemo. While those tools keep our components performant, our codebase often suffers from another issue: logic duplication.

In this lesson, we move beyond built-in hooks and start building our own. By learning how to implement custom hooks, you’ll gain the ability to move complex, recurring logic out of your UI components and into reusable, testable functions.

Why Custom Hooks?

In a dashboard application, you’ll frequently find yourself writing the same state management logic across multiple components. Perhaps you're managing a "loading" toggle, tracking window dimensions, or syncing form fields with internal state.

If you copy-paste that logic, you create a maintenance nightmare. If you need to change how that logic works later, you have to find and update it in five different places. Custom hooks provide a way to perform abstraction, allowing you to encapsulate that logic and share it across your entire application. Think of this as the React equivalent of the Laravel helpers: How to build and use custom global functions pattern—it's about making your core logic reusable and clean.

Identifying Logic to Extract

A good candidate for a custom hook is any logic that uses one or more built-in hooks (useState, useEffect, useRef, etc.) and repeats across your project.

Look for these "code smells" in your components:

  1. Shared State: Multiple components need to track the same kind of data.
  2. Side Effects: Components have identical useEffect setups (e.g., event listeners or API subscriptions).
  3. Complex State: A component’s useState logic is so dense it obscures the actual UI rendering.

Worked Example: Creating a useToggle Hook

Let's look at a common pattern: toggling a boolean value, like a modal visibility state or a "dark mode" switch.

Before: Repetitive Logic

JSX
function SettingsModal() {
  const [isOpen, setIsOpen] = useState(false);
  const toggle = () => setIsOpen(prev => !prev);

  return <button onClick={toggle}>{isOpen ? CE9178">'Close' : CE9178">'Open'}</button>;
}

If we have three different components that need this, we're repeating useState and the toggle function.

After: The Custom Hook

We can extract this into a function named useToggle. By convention, custom hooks must start with the word use.

JAVASCRIPT
// hooks/useToggle.js
import { useState } from CE9178">'react';

export function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  const toggle = () => setValue((prev) => !prev);
  
  return [value, toggle];
}

Now, our component becomes significantly cleaner:

JSX
import { useToggle } from CE9178">'./hooks/useToggle';

function SettingsModal() {
  const [isOpen, toggle] = useToggle(false);

  return <button onClick={toggle}>{isOpen ? CE9178">'Close' : CE9178">'Open'}</button>;
}

This is the essence of Refactoring for Clean Code: Improving React Maintainability. We have successfully abstracted the state management away from the UI.

The Rules of Hooks

Because custom hooks use built-in hooks under the hood, they are bound by the same Rules of Hooks:

  1. Only call hooks at the top level: Never call a custom hook inside loops, conditions, or nested functions. React relies on the call order to track state.
  2. Only call hooks from React functions: You can call custom hooks from components or other custom hooks, but not from regular JavaScript functions or class components.

Hands-on Exercise

In our dashboard project, we often need to track if a component is currently "hovered."

  1. Create a new file hooks/useHover.js.
  2. Use useState to track a boolean isHovered.
  3. Use useRef to target the element.
  4. Implement useEffect to attach mouseover and mouseout event listeners to that element.
  5. Return the isHovered boolean and the ref object.

Hint: Remember that useEffect cleanup is essential to remove those event listeners when the component unmounts.

Common Pitfalls

  • Naming Conventions: If you name your function toggle instead of useToggle, React’s linting tools won't know it's a hook, and you won't get warnings if you violate the Rules of Hooks. Always prefix with use.
  • Over-abstraction: Don't turn every 2-line state change into a hook. If the logic is specific to one component and won't be reused, keep it inside the component to avoid unnecessary complexity.
  • Stale Closures: If your custom hook uses useEffect with dependencies, ensure those dependencies are passed correctly. If you're struggling with stale data, revisit Persistent Mutable Values with useRef.

Recap

Custom hooks are the primary mechanism for code reuse in modern React. By extracting stateful logic, you keep your components focused on rendering UI rather than managing complex side effects. As you continue to build your dashboard, look for opportunities to turn repeated logic into clean, reusable abstractions.

Up next: We will apply these principles to create a persistent useLocalStorage hook, enabling our dashboard settings to survive page reloads.

Similar Posts