ReactThe useState Hook

The useState Hook

useState is the most fundamental React hook. It lets you add reactive state to a function component — a value that persists between renders and causes the component to update whenever it changes. You will use it in virtually every React component you write.

Anatomy of useState

JSX
import { useState } from 'react'

// const [stateValue, setterFunction] = useState(initialValue)
const [count, setCount] = useState(0)

useState returns an array of exactly two elements, and you always destructure them immediately:

  • count — the current state value. On the first render it equals initialValue (here 0). On subsequent renders React provides the latest value.

  • setCount — the setter function. Call it with a new value to schedule a re-render with that value. By convention, name it set + the state variable name.

  • 0 — the initial value. React uses this only on the very first render. After that the initial value is ignored completely.

A Counter — the Classic Example

JSX
import { useState } from 'react'

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}
Note
The initial value is only used on the first render. If you pass a prop as the initial value — `useState(props.defaultCount)` — changing that prop later does NOT update the state. This is intentional and a common gotcha.
useState with Different Types

useState accepts any JavaScript value as the initial state. Here are the most common patterns:

JSX
// Boolean — toggle state
const [isOpen, setIsOpen] = useState(false)
// Toggle: setIsOpen(!isOpen)   or   setIsOpen(prev => !prev)

// String — text input or selection
const [query, setQuery] = useState('')
const [theme, setTheme] = useState('light')

// Number — counters, pagination
const [page, setPage] = useState(1)
const [zoom, setZoom] = useState(100)

// Object — a form or a record
const [user, setUser] = useState({ name: '', email: '' })
// Update one field:
// setUser({ ...user, name: 'Alice' })

// Array — a list of items
const [todos, setTodos] = useState([])
// Add an item:
// setTodos([...todos, newTodo])
Multiple State Variables

You can (and should) call useState multiple times for independent pieces of state. React tracks them by the order they are called — which is why hooks must always be called at the top level, never inside conditions or loops:

JSX
function SearchBar() {
  const [query, setQuery]     = useState('')
  const [page, setPage]       = useState(1)
  const [isLoading, setLoading] = useState(false)
  const [results, setResults] = useState([])

  async function handleSearch(e) {
    e.preventDefault()
    setLoading(true)
    setPage(1)
    const data = await fetchResults(query)
    setResults(data)
    setLoading(false)
  }

  return (
    <form onSubmit={handleSearch}>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Searching…' : 'Search'}
      </button>
      {results.map((r) => <p key={r.id}>{r.title}</p>)}
    </form>
  )
}
Tip
Group related state variables into a single object when they always change together. For independent values — like `query`, `page`, and `isLoading` — keep them as separate `useState` calls. It makes the code easier to reason about and the diffs cleaner.
Toggle Example

JSX
function ThemeToggle() {
  const [isDark, setIsDark] = useState(false)

  return (
    <button
      onClick={() => setIsDark((prev) => !prev)}
      style={{
        background: isDark ? '#1a1a1a' : '#f5f5f5',
        color: isDark ? '#fff' : '#000',
        padding: '8px 16px',
        border: 'none',
        borderRadius: 4,
        cursor: 'pointer',
      }}
    >
      Switch to {isDark ? 'Light' : 'Dark'} Mode
    </button>
  )
}
Controlled Input Example

JSX
function NameInput() {
  const [name, setName] = useState('')

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>
        {name
          ? `Hello, ${name}!`
          : 'Start typing above…'}
      </p>
    </div>
  )
}
Lazy Initialization

If computing the initial state is expensive (reading from localStorage, parsing JSON, running a big calculation), pass a function instead of a value. React calls it once on the first render and ignores it afterwards:

JSX
// ✗ Called on every render even though the result is only used once:
const [settings, setSettings] = useState(JSON.parse(localStorage.getItem('settings')))

// ✓ Lazy initializer — the function is only called on the first render:
const [settings, setSettings] = useState(() => {
  const saved = localStorage.getItem('settings')
  return saved ? JSON.parse(saved) : { theme: 'light', language: 'en' }
})
State Updates Are Asynchronous

Calling a setter does not immediately change the state variable. React batches updates and applies them before the next render. This means the variable still holds the old value for the remainder of the current event handler:

JSX
function Example() {
  const [count, setCount] = useState(0)

  function handleClick() {
    setCount(count + 1)
    console.log(count)   // Still 0! The update hasn't been applied yet.
    // React will re-render the component AFTER this function returns,
    // and 'count' will be 1 in the next render.
  }

  return <button onClick={handleClick}>{count}</button>
}
Warning
Do not read a state variable immediately after calling its setter and expect the new value. The new value is available in the next render. Use functional updates (`setCount(prev => prev + 1)`) when you need to build on the current value safely.
  • useState(initialValue) returns [currentValue, setterFn]

  • The initial value is used only on the first render

  • Calling the setter schedules a re-render with the new value

  • Multiple useState calls create independent state variables

  • Pass a function to useState for expensive initial computations (lazy init)

  • State updates are batched and asynchronous — the variable keeps its old value until the next render