JSTAcademy
0 XP
Dashboard
Crash Courses
Custom Hooks
10 min
Masters+175 XP
Crash Courses · Masters

Custom Hooks

Extract and reuse stateful logic with custom hooks.
10 min read+175 XP on completionCert: React Crash Course
Tap any word in the text below to start reading from there.

Custom Hooks

Custom hooks extract stateful logic into reusable functions. They follow the "use" naming convention and can call other hooks.

useLocalStorage

import { useState, useEffect } from 'react'

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const stored = localStorage.getItem(key)
      return stored ? JSON.parse(stored) : initialValue
    } catch { return initialValue }
  })

  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(value))
    } catch { /* storage full */ }
  }, [key, value])

  return [value, setValue] as const
}

// Usage
function App() {
  const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light')
}

useFetch

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    let cancelled = false

    fetch(url)
      .then(r => r.json())
      .then(d => { if (!cancelled) setData(d) })
      .catch(e => { if (!cancelled) setError(e.message) })
      .finally(() => { if (!cancelled) setLoading(false) })

    return () => { cancelled = true }
  }, [url])

  return { data, loading, error }
}

// Usage
function CourseDetail({ id }: { id: string }) {
  const { data: course, loading, error } = useFetch<Course>(`/api/courses/${id}`)

  if (loading) return <Spinner />
  if (error) return <Error message={error} />
  return <CourseView course={course!} />
}

useDebounce

function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value)

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(timer)
  }, [value, delay])

  return debounced
}

// Usage — search input that waits 300ms before searching
function SearchInput() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 300)

  useEffect(() => {
    if (debouncedQuery) search(debouncedQuery)
  }, [debouncedQuery])

  return <input value={query} onChange={e => setQuery(e.target.value)} />
}
0%