Share:

Hooks

advanced

Part of Advanced React

Theory

Hooks are functions that let you hook into React state and lifecycle features from functional components. Introduced in React 16.8, they revolutionized how developers write React code by eliminating the need for class components.

What are Hooks?

Before hooks, stateful logic required class components with complex lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. Hooks simplify all of this into clean, reusable functions.

Common hooks include:

  • useState — add state to a component
  • useEffect — perform side effects (data fetching, subscriptions, DOM manipulation)
  • useRef — hold mutable values that persist across renders
  • useMemo — memoize expensive computations
  • useCallback — memoize callback functions

Rules of Hooks

Two essential rules govern hooks:

  1. Only call hooks at the top level. Do not call hooks inside loops, conditions, or nested functions. This ensures that hooks are called in the same order every time a component renders.
// WRONG
if (condition) {
  useState(0) // Conditional hook call!
}
 
// RIGHT
const [value, setValue] = useState(0)
  1. Only call hooks from React functions. Call them from functional components or custom hooks, not from regular JavaScript functions.

These rules exist because React relies on the order of hook calls to correctly associate state with each hook. Breaking the order breaks the component.

useState Deep Dive

useState can hold any type of data: primitives, objects, arrays, or even functions for lazy initialization:

// Lazy initial state — function is only called on the first render
const [data, setData] = useState(() => {
  const initial = expensiveComputation()
  return initial
})

State updates are batched in React 18+. Multiple setState calls within the same event handler are batched into a single re-render:

const [a, setA] = useState(0)
const [b, setB] = useState(0)
 
// These two updates are batched — only one re-render
setA(prev => prev + 1)
setB(prev => prev + 1)

useEffect — Side Effects in React

useEffect lets you perform side effects in your components. It runs after the browser has painted the screen.

useEffect(() => {
  // Side effect code here
  document.title = `You clicked ${count} times`
}, [count]) // Only re-run when 'count' changes

The dependency array controls when the effect runs:

  • No array: Runs after every render
  • Empty array []: Runs only once (on mount)
  • With values [a, b]: Runs when a or b change

Cleanup Function

If your effect creates subscriptions, timers, or event listeners, return a cleanup function:

useEffect(() => {
  const timer = setInterval(() => {
    setTime(new Date())
  }, 1000)
 
  return () => clearInterval(timer) // Cleanup on unmount
}, [])

Common Use Cases

// Fetching data
useEffect(() => {
  fetch('/api/users')
    .then(res => res.json())
    .then(data => setUsers(data))
}, [])
 
// Subscribing to events
useEffect(() => {
  const handler = () => console.log('resized')
  window.addEventListener('resize', handler)
  return () => window.removeEventListener('resize', handler)
}, [])
 
// Syncing with localStorage
useEffect(() => {
  localStorage.setItem('theme', theme)
}, [theme])

Custom Hooks

Custom hooks let you extract component logic into reusable functions. A custom hook is a JavaScript function whose name starts with use and that may call other hooks.

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth)
 
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth)
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])
 
  return width
}
 
// Usage in a component:
function MyComponent() {
  const width = useWindowWidth()
  return <p>Window width: {width}px</p>
}

Custom hooks are the primary mechanism for sharing stateful logic between components.

useRef

useRef returns a mutable object that persists across renders without causing re-renders when changed:

function Timer() {
  const intervalRef = useRef(null)
 
  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      console.log('tick')
    }, 1000)
  }
 
  const stopTimer = () => {
    clearInterval(intervalRef.current)
  }
 
  return (
    <>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </>
  )
}

Refs are commonly used to access DOM elements directly:

function TextInput() {
  const inputRef = useRef(null)
 
  const focusInput = () => {
    inputRef.current.focus()
  }
 
  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </>
  )
}

useMemo and useCallback

useMemo memoizes the result of an expensive computation:

const sortedList = useMemo(() => {
  return items.sort((a, b) => a.name.localeCompare(b.name))
}, [items])

useCallback memoizes a function definition:

const handleClick = useCallback(() => {
  console.log('Clicked!')
}, []) // Same function instance across renders

These are optimization tools. Use them only when you have measured performance issues.

Don't wrap everything in useMemo or useCallback. Premature optimization can make your code harder to read. Measure first, optimize second.

Practical Examples

Example 1: Custom Hook for Fetching Data
jsx
Example 2: useRef for Focus Management
jsx

Use useRef when you need to store a value that should persist across renders but shouldn't trigger a re-render when it changes. Refs are perfect for timers, DOM references, and tracking mutable values.

Exercises

Build a useLocalStorage Hook

medium

Create a custom hook called useLocalStorage that works like useState but persists the value to localStorage. It should accept a key and an initial value. The hook should read from localStorage on initialization and write to localStorage on every update.

Expected Output:

The input value is persisted to localStorage. Refreshing the page restores the saved value.

useEffect with Cleanup

medium

Create a MouseTracker component that displays the current mouse position (X, Y coordinates). Add a toggle button to start and stop tracking. Use useEffect with proper cleanup to remove the event listener when tracking is off.

Expected Output:

A page that shows mouse coordinates when tracking is enabled and stops updating when tracking is disabled

Create a useDebounce Hook

hard

Create a custom hook called useDebounce that debounces a value. It should accept the value and a delay in milliseconds. The debounced value should only update after the specified delay has passed since the last change.

Expected Output:

The console log only fires 500ms after the user stops typing. Rapid typing should not trigger multiple requests.

Mini Quiz

Mini Quiz

Mini Project

Mini Project: Real-Time Search with Debounce

Build a product search page that fetches results from a mock API. Use custom hooks (useDebounce, useFetch), useEffect for data fetching with cleanup, and useRef for managing the search input focus.

Requirements:

    Bonus Challenge

    Add highlighted search terms in the results. Also add a 'Clear' button that resets the input and refocuses it.