SyntaxStudy
Sign Up
React Beginner 2 min read

useState Hook Basics

The `useState` hook is the primary tool for adding reactive local state to a function component. You call it with an initial value and receive a pair: the current state value and a setter function. When the setter is called with a new value, React schedules a re-render and the component function runs again with the updated state. State is preserved between renders — unlike ordinary variables, which are recreated on every call. The initial value passed to `useState` is only used on the very first render. For expensive computations, you can pass a function instead of a value — React calls it once on mount and ignores it on subsequent renders (lazy initialisation). The setter function can receive either a new value directly or an updater function of the form `prev => newValue`. The updater form is always safer when the new value depends on the previous one, especially inside asynchronous code or batched updates. State updates are asynchronous — the new value is not visible in the current render cycle, only in the next one. Attempting to read state immediately after calling its setter still returns the old value. For multiple related state values you can use several `useState` calls or a single `useReducer`. React guarantees that setter function references are stable across renders, so they are safe to include in dependency arrays.
Example
import { useState } from 'react';

// 1. Basic counter
function Counter() {
  const [count, setCount] = useState(0); // initial value: 0

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>−</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

// 2. Updater function form (safe for async / batched updates)
function SafeCounter() {
  const [count, setCount] = useState(0);

  const incrementThrice = () => {
    // All three updates are batched — using updater ensures correct result
    setCount(c => c + 1);
    setCount(c => c + 1);
    setCount(c => c + 1);
    // Without updater form, all three would read the same stale `count`
  };

  return <button onClick={incrementThrice}>+3 (now: {count})</button>;
}

// 3. Lazy initialisation — function runs only once
function LocalStorageCounter() {
  const [count, setCount] = useState(() => {
    const stored = localStorage.getItem('count');
    return stored ? Number(stored) : 0;
  });

  const increment = () => {
    const next = count + 1;
    setCount(next);
    localStorage.setItem('count', next);
  };

  return <button onClick={increment}>Persisted count: {count}</button>;
}

export default Counter;