Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /React
  4. /Custom Hooks
Your Progress0%
0 of 42 completed

React Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App

Custom Hooks

Creating reusable logic with custom hooks

You've learned React's built-in hooks like useState, useEffect, and useContext. But the real power of hooks is that you can create your own! Custom hooks let you extract component logic into reusable functions. Instead of copying and pasting the same useState and useEffect combinations across components, you can package them into a custom hook and reuse it everywhere. Let's learn how to build your own hooks! šŸŖ

What Are Custom Hooks?

A custom hook is a JavaScript function whose name starts with "use" and that may call other hooks:

custom-hook-basic.jsx
// A custom hook
function useWindowWidth() {
  const [width, setWidth] = React.useState(window.innerWidth)
  
  React.useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth)
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])
  
  return width
}

// Use in any component
function MyComponent() {
  const width = useWindowWidth()
  
  return <div>Window width: {width}px</div>
}

// Key characteristics:
// 1. Name starts with "use"
// 2. Can call other hooks (useState, useEffect, etc.)
// 3. Returns whatever you want (value, object, array)
// 4. Reusable across components

Why "use" Prefix?

The "use" prefix is a React convention that:
• Tells React this function is a hook
• Enables React's linter to check hook rules
• Makes it clear this function uses React features

Without "use", React can't enforce hook rules!

The Problem: Duplicated Logic

Without custom hooks, you repeat the same logic in multiple components:

problem-duplication.jsx
// āŒ Component 1: Duplicated logic
function UserProfile() {
  const [user, setUser] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => {
        setUser(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err.message)
        setLoading(false)
      })
  }, [])
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  return <div>{user.name}</div>
}

// āŒ Component 2: Same logic copied!
function UserSettings() {
  const [user, setUser] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => {
        setUser(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err.message)
        setLoading(false)
      })
  }, [])
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  return <div>{user.email}</div>
}

// Problems:
// 1. Same code in multiple places
// 2. Hard to maintain (fix bug in one, forget the other)
// 3. More code to write
// 4. Easy to make mistakes

The Solution: Extract to Custom Hook

Extract the shared logic into a custom hook:

solution-custom-hook.jsx
// āœ… Custom hook - Reusable logic
function useUser() {
  const [user, setUser] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => {
        setUser(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err.message)
        setLoading(false)
      })
  }, [])
  
  return { user, loading, error }
}

// āœ… Component 1: Clean and simple
function UserProfile() {
  const { user, loading, error } = useUser()
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  return <div>{user.name}</div>
}

// āœ… Component 2: Same hook, no duplication
function UserSettings() {
  const { user, loading, error } = useUser()
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  return <div>{user.email}</div>
}

// Benefits:
// 1. Logic written once, used many times
// 2. Fix bugs in one place
// 3. Less code in components
// 4. Easier to test and maintain

Rules for Custom Hooks

  1. Name must start with "use"
rule-naming.jsx
// āœ… Good
function useWindowWidth() { }
function useLocalStorage() { }
function useFetch() { }

// āŒ Bad
function getWindowWidth() { }  // Missing "use"
function windowWidth() { }     // Missing "use"
function WindowWidth() { }     // Component, not hook
  1. Can only call hooks at the top level
rule-top-level.jsx
function useCustomHook() {
  // āœ… Good - Top level
  const [state, setState] = React.useState(0)
  
  // āŒ Bad - Inside condition
  if (condition) {
    const [count, setCount] = React.useState(0)
  }
  
  // āŒ Bad - Inside loop
  for (let i = 0; i < 5; i++) {
    React.useEffect(() => {})
  }
  
  return state
}
  1. Can call other hooks
rule-call-hooks.jsx
function useCustomHook() {
  // āœ… Can call any React hooks
  const [state, setState] = React.useState(0)
  const ref = React.useRef(null)
  
  React.useEffect(() => {
    // Effect logic
  }, [])
  
  // āœ… Can call other custom hooks
  const data = useAnotherCustomHook()
  
  return state
}

āš ļø Hook Rules Apply

Custom hooks must follow the same rules as built-in hooks:
• Only call at top level
• Only call from React functions
• Can call other hooks

The ESLint plugin checks these rules automatically!

Common Custom Hook Patterns

1. useFetch - Data Fetching

pattern-fetch.jsx
function useFetch(url) {
  const [data, setData] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    let ignore = false
    
    setLoading(true)
    setError(null)
    
    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error('Failed to fetch')
        return res.json()
      })
      .then(data => {
        if (!ignore) {
          setData(data)
          setLoading(false)
        }
      })
      .catch(err => {
        if (!ignore) {
          setError(err.message)
          setLoading(false)
        }
      })
    
    return () => {
      ignore = true
    }
  }, [url])
  
  return { data, loading, error }
}

// Usage:
function UserList() {
  const { data: users, loading, error } = useFetch('/api/users')
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  )
}

2. useLocalStorage - Persistent State

pattern-localstorage.jsx
function useLocalStorage(key, initialValue) {
  // Get initial value from localStorage or use initialValue
  const [storedValue, setStoredValue] = React.useState(() => {
    try {
      const item = window.localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      console.error(error)
      return initialValue
    }
  })
  
  // Return wrapped version of setState that also updates localStorage
  const setValue = (value) => {
    try {
      // Allow value to be a function like useState
      const valueToStore = value instanceof Function ? value(storedValue) : value
      
      setStoredValue(valueToStore)
      window.localStorage.setItem(key, JSON.stringify(valueToStore))
    } catch (error) {
      console.error(error)
    }
  }
  
  return [storedValue, setValue]
}

// Usage: Just like useState, but persists!
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('theme', 'light')
  
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current theme: {theme}
    </button>
  )
}

// Theme persists across page refreshes!

3. useToggle - Boolean State

pattern-toggle.jsx
function useToggle(initialValue = false) {
  const [value, setValue] = React.useState(initialValue)
  
  const toggle = React.useCallback(() => {
    setValue(v => !v)
  }, [])
  
  return [value, toggle]
}

// Usage:
function Modal() {
  const [isOpen, toggleOpen] = useToggle(false)
  
  return (
    <div>
      <button onClick={toggleOpen}>Toggle Modal</button>
      {isOpen && (
        <div className="modal">
          <p>Modal content</p>
          <button onClick={toggleOpen}>Close</button>
        </div>
      )}
    </div>
  )
}

// Simpler than useState for boolean toggles!

4. useDebounce - Delayed Value

pattern-debounce.jsx
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = React.useState(value)
  
  React.useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)
    
    return () => {
      clearTimeout(handler)
    }
  }, [value, delay])
  
  return debouncedValue
}

// Usage:
function SearchBox() {
  const [searchTerm, setSearchTerm] = React.useState('')
  const debouncedSearchTerm = useDebounce(searchTerm, 500)
  
  React.useEffect(() => {
    if (debouncedSearchTerm) {
      // API call here
      console.log('Searching for:', debouncedSearchTerm)
    }
  }, [debouncedSearchTerm])
  
  return (
    <input
      value={searchTerm}
      onChange={e => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  )
}

// Only searches after user stops typing for 500ms!

5. usePrevious - Previous Value

pattern-previous.jsx
function usePrevious(value) {
  const ref = React.useRef()
  
  React.useEffect(() => {
    ref.current = value
  })
  
  return ref.current
}

// Usage:
function Counter() {
  const [count, setCount] = React.useState(0)
  const prevCount = usePrevious(count)
  
  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

6. useOnClickOutside - Click Detection

pattern-click-outside.jsx
function useOnClickOutside(ref, handler) {
  React.useEffect(() => {
    const listener = (event) => {
      // Do nothing if clicking ref's element or descendants
      if (!ref.current || ref.current.contains(event.target)) {
        return
      }
      handler(event)
    }
    
    document.addEventListener('mousedown', listener)
    document.addEventListener('touchstart', listener)
    
    return () => {
      document.removeEventListener('mousedown', listener)
      document.removeEventListener('touchstart', listener)
    }
  }, [ref, handler])
}

// Usage:
function Dropdown() {
  const [isOpen, setIsOpen] = React.useState(false)
  const dropdownRef = React.useRef(null)
  
  useOnClickOutside(dropdownRef, () => setIsOpen(false))
  
  return (
    <div ref={dropdownRef}>
      <button onClick={() => setIsOpen(!isOpen)}>Menu</button>
      {isOpen && (
        <ul>
          <li>Option 1</li>
          <li>Option 2</li>
        </ul>
      )}
    </div>
  )
}

// Dropdown closes when clicking outside!

Interactive Demo

ReactPlayground.jsx
Code Editor
Preview

šŸ’” Tip: Edit the code above and click "Run" to see your changes

Advanced Custom Hook Patterns

Pattern 1: Hook with Parameters

advanced-params.jsx
function useFetch(url, options = {}) {
  const [data, setData] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    let ignore = false
    
    fetch(url, options)
      .then(res => res.json())
      .then(data => {
        if (!ignore) setData(data)
      })
      .catch(err => {
        if (!ignore) setError(err)
      })
      .finally(() => {
        if (!ignore) setLoading(false)
      })
    
    return () => {
      ignore = true
    }
  }, [url, options])  // Re-fetch when url or options change
  
  return { data, loading, error }
}

// Usage with different parameters:
const users = useFetch('/api/users')
const posts = useFetch('/api/posts', { method: 'POST' })

Pattern 2: Hook Returning Multiple Functions

advanced-multiple.jsx
function useCounter(initialValue = 0) {
  const [count, setCount] = React.useState(initialValue)
  
  const increment = React.useCallback(() => setCount(c => c + 1), [])
  const decrement = React.useCallback(() => setCount(c => c - 1), [])
  const reset = React.useCallback(() => setCount(initialValue), [initialValue])
  const set = React.useCallback((value) => setCount(value), [])
  
  return {
    count,
    increment,
    decrement,
    reset,
    set
  }
}

// Usage:
function Counter() {
  const { count, increment, decrement, reset } = useCounter(0)
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

Pattern 3: Composing Hooks

advanced-compose.jsx
// Base hooks
function useLocalStorage(key, initialValue) {
  // ... implementation
}

function useMedia(query) {
  const [matches, setMatches] = React.useState(
    window.matchMedia(query).matches
  )
  
  React.useEffect(() => {
    const media = window.matchMedia(query)
    const listener = () => setMatches(media.matches)
    
    media.addListener(listener)
    return () => media.removeListener(listener)
  }, [query])
  
  return matches
}

// Composed hook using both
function useDarkMode() {
  const [enabledState, setEnabledState] = useLocalStorage('dark-mode', false)
  const prefersDarkMode = useMedia('(prefers-color-scheme: dark)')
  
  const enabled = enabledState ?? prefersDarkMode
  
  React.useEffect(() => {
    document.body.classList.toggle('dark-mode', enabled)
  }, [enabled])
  
  return [enabled, setEnabledState]
}

// Usage: One hook with combined functionality!
function App() {
  const [darkMode, setDarkMode] = useDarkMode()
  
  return (
    <button onClick={() => setDarkMode(!darkMode)}>
      Toggle {darkMode ? 'Light' : 'Dark'} Mode
    </button>
  )
}

Custom Hook Best Practices

  • Name with "use" prefix - Required for hook rules
  • Keep hooks focused - One responsibility per hook
  • Return what's useful - State, functions, or both
  • Use array/object returns - Array for simple, object for many values
  • Document parameters - JSDoc comments help users
  • Handle edge cases - Null checks, error handling
  • Make hooks composable - Can use other hooks
  • Memoize callbacks - Use useCallback for functions
  • Test custom hooks - Use React Testing Library
  • Extract when you see duplication - Don't over-abstract

When to Create a Custom Hook:

Create a custom hook when:
• You copy-paste the same useState/useEffect pattern
• Logic is used in 2+ components
• Complex logic clutters your component
• You want to share logic across projects

Don't create one for:
• One-time use cases
• Very simple logic (one useState)
• When a helper function would work

Common Custom Hook Mistakes

1. Forgetting "use" Prefix

mistake-prefix.jsx
// āŒ Wrong - Missing "use"
function fetchData() {
  const [data, setData] = React.useState(null)
  // ... React won't enforce hook rules!
  return data
}

// āœ… Correct - Starts with "use"
function useFetchData() {
  const [data, setData] = React.useState(null)
  // ... Hook rules enforced
  return data
}

2. Not Handling Cleanup

mistake-cleanup.jsx
// āŒ Wrong - No cleanup
function useInterval(callback, delay) {
  React.useEffect(() => {
    const id = setInterval(callback, delay)
    // Missing cleanup!
  }, [callback, delay])
}

// āœ… Correct - Cleanup interval
function useInterval(callback, delay) {
  React.useEffect(() => {
    const id = setInterval(callback, delay)
    return () => clearInterval(id)  // Cleanup!
  }, [callback, delay])
}

3. Creating New Objects in Dependencies

mistake-objects.jsx
// āŒ Wrong - options object recreated every render
function useFetch(url, options) {
  React.useEffect(() => {
    fetch(url, options)
  }, [url, options])  // options is always "new"!
}

// āœ… Better - Destructure specific values
function useFetch(url, method = 'GET') {
  React.useEffect(() => {
    fetch(url, { method })
  }, [url, method])  // Only re-fetch when method changes
}

// āœ… Or use useMemo
function Component() {
  const options = React.useMemo(() => ({
    method: 'POST'
  }), [])
  
  const data = useFetch('/api/data', options)
}

Key Takeaways

  • Custom hooks extract and reuse component logic
  • Name must start with "use" prefix
  • Can call other React hooks and custom hooks
  • Must follow all hook rules (top level, React functions only)
  • Return values, functions, or objects as needed
  • Great for data fetching, localStorage, toggle state, etc.
  • Reduces code duplication across components
  • Makes logic testable in isolation
  • Can compose multiple hooks together
  • Each hook should have one clear responsibility
  • Document parameters and return values
  • Handle cleanup in useEffect properly
  • Custom hooks are functions, not components

What's Next?

You now know how to create custom hooks to extract and reuse component logic! You can build a library of reusable hooks for common patterns.

In the next lesson, you'll learn about Higher-Order Components (HOCs):

  • What are Higher-Order Components
  • Creating HOCs to enhance components
  • Common HOC patterns
  • When to use HOCs vs hooks
  • HOC best practices

Get ready to learn another powerful React pattern! šŸš€

Learning how to create reusable logic with custom hooks in React!

Previous
useCallback Hook
Next
Higher-Order Components

Never Miss a New React Tutorial

Join 2,000+ developers learning React step-by-step. Get new tutorials, tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

React Tutorials

0 of 42 completed

Your Progress0%

Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

Ā© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo