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.
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:
- Shared State: Multiple components need to track the same kind of data.
- Side Effects: Components have identical
useEffectsetups (e.g., event listeners or API subscriptions). - Complex State: A component’s
useStatelogic 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
JSXfunction 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:
JSXimport { 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:
- 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.
- 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."
- Create a new file
hooks/useHover.js. - Use
useStateto track a booleanisHovered. - Use
useRefto target the element. - Implement
useEffectto attachmouseoverandmouseoutevent listeners to that element. - Return the
isHoveredboolean 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
toggleinstead ofuseToggle, 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 withuse. - 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
useEffectwith 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.
Work with me

React & Next.js Dashboard / Admin UI Development
A clean, data-rich dashboard UI in React or Next.js — charts, tables, and real-time data that your users will actually enjoy using.

Next.js Website & Landing Page Development
A blazing-fast, SEO-optimized website or landing page in Next.js — the kind that loads instantly and ranks. Design-to-code, done right.