Skip to content
FLAVIO COPES
flaviocopes.com
2026

Introduction to React Hooks

By

Learn how React Hooks let function components hold state with useState, run side effects with useEffect, and share logic through your own custom hooks.

~~~

Hooks are functions that let React components use state and other React features.

They became stable in React 16.8. Today they are the normal way to write stateful function components.

Hooks did not make class components invalid. Existing class components still work, and you can use classes and function components in the same application.

Store state with useState

useState() adds a state variable to a component.

Pass its initial value. React returns the current state and a setter function:

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    <button onClick={() => setCount(value => value + 1)}>
      Count is {count}
    </button>
  )
}

The array destructuring gives the two returned values useful names.

Calling setCount() asks React to render the component again with the new state. The updater function receives the previous value, which is useful when the next value depends on it.

You can call useState() more than once:

const [name, setName] = useState('Flavio')
const [subscribed, setSubscribed] = useState(false)

Each call creates an independent piece of state.

Read the official useState reference for initializers, updater functions, and common mistakes.

Follow the Rules of Hooks

React depends on Hooks being called in the same order on every render.

Follow these two rules:

This is not valid:

if (subscribed) {
  const [email, setEmail] = useState('')
}

Move the Hook to the top level and put the condition around the behavior that uses it.

The official Rules of Hooks explain why the call order matters. The React Hooks ESLint rules can catch these mistakes automatically.

Synchronize with an external system using useEffect

useEffect() is for synchronizing a component with something outside React.

Examples include a browser event, timer, network connection, or third-party widget.

Do not think of an Effect as a direct replacement for componentDidMount() or another class lifecycle method. Think about the external process you need to start and stop.

For example, this component keeps a chat connection synchronized with roomId:

import { useEffect } from 'react'
import { createConnection } from './chat.js'

export default function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId)
    connection.connect()

    return () => {
      connection.disconnect()
    }
  }, [roomId])

  return <h1>Room: {roomId}</h1>
}

The function returned by the Effect is its cleanup.

React runs the cleanup before synchronizing again with a changed roomId. It also runs the cleanup when the component is removed.

Effect dependencies are not optional choices

The dependency array lists every reactive value read by the Effect.

In the previous example, the Effect reads roomId, so [roomId] is the correct dependency list.

If you leave out the dependency array, the Effect runs after every commit:

useEffect(() => {
  logPageView()
})

An empty array means the Effect does not read changing values from the component:

useEffect(() => {
  const id = setInterval(tick, 1000)
  return () => clearInterval(id)
}, [])

In development, Strict Mode runs an extra setup and cleanup cycle. This helps reveal Effects that do not clean up correctly.

Do not remove dependencies just to make an Effect run less often. Change the surrounding code so the Effect no longer reads that reactive value.

The official useEffect reference documents the exact setup, cleanup, and dependency behavior.

Also remember this: if you are not synchronizing with an external system, you probably do not need an Effect.

Share logic with a custom Hook

A custom Hook is a JavaScript function whose name starts with use and which calls other Hooks.

Here is a small useToggle() Hook:

import { useState } from 'react'

export function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue)

  function toggle() {
    setValue(current => !current)
  }

  return [value, toggle]
}

Use it inside a component:

import { useToggle } from './useToggle.js'

export default function Details() {
  const [open, toggle] = useToggle()

  return (
    <>
      <button onClick={toggle}>Toggle details</button>
      {open && <p>Here are the details.</p>}
    </>
  )
}

Custom Hooks share stateful logic, not the state itself. Two components that call useToggle() get two independent state values.

The official custom Hooks guide explains when extracting a Hook makes code clearer.

Not sure which hook fits your use case? Try my free React hook chooser: describe what you want to do and it suggests the right hook, with a snippet.

Tagged: React ยท All topics
~~~

Related posts about react: