Chapter 6 of 20 · React for Beginners — Complete Free Tutorial

State with useState

Props let the React for Beginners series pass data into a component, but a component that never changes is a static image. State is data a component owns and can update — a counter, a toggle, the text in a search box. This chapter introduces the useState hook, the rules for updating state immutably, and what actually happens on screen when state changes.

Props vs state

Both are plain JavaScript, but they answer different questions. Props describe inputs from outside; state describes values that change during the component's life.

PropsState
Who owns itThe parent componentThe component itself
Can it change?Read-only inside the childYes — through its setter function
Changes whenParent re-renders with new valuesYou call the setter
Typical useLabels, ids, configuration, callbacksCounters, form values, toggles, fetched data

Your first useState call

useState is a hook: a function React provides that lets a component "hook into" React's state machinery. Call it at the top of your component. It returns a two-item array — the current value and a function that updates it — which you unpack with destructuring:

import { useState } from 'react';

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

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicks: {count}
    </button>
  );
}

Expected output:

Clicks: 0     ← first render
Clicks: 1     ← after one click
Clicks: 2     ← after two clicks

The argument to useState(0) is the initial value — used only on the first render. The setter name follows a convention: set plus the state variable's name in camelCase.

Updating state re-renders

Calling setCount does not immediately redraw. It schedules an update; React then re-runs your component function with the new value and diffs the result against what is on screen. Two consequences surprise beginners:

  • The component function runs again — every render creates fresh local variables; only state persists between renders.
  • React batches updates — several setter calls in one event handler produce a single re-render, keeping updates fast.

Never mutate — replace

State must be treated as immutable. Instead of editing the current object or array, pass a new one built from the old (spread syntax from arrays and array methods makes this easy):

const [tags, setTags] = useState(['react', 'vite']);

function addTag(tag) {
  setTags([...tags, tag]);        // ✅ new array
  // tags.push(tag);               // ❌ mutates current state — React may skip the update
}

function renameFirst() {
  setTags((prev) => prev.map((t, i) => (i === 0 ? 'hooks' : t)));
}

Expected output: after addTag('jsx') the list renders react, vite, jsx; after renameFirst() it renders hooks, vite, jsx. Because every update builds a new array, React always sees a change.

Functional updates for the next value

When the new value depends on the old one, pass a function instead of a value. React hands you the latest previous state, which stays correct even if updates queue up:

setCount(count + 1);        // fine for simple clicks
setCount((prev) => prev + 1);   // ✅ always uses the newest value

Expected output: both produce the same single increment on screen, but only the second is safe when several updates happen in one render cycle — rapid clicks, timers, or batched events.

Multiple state values

You can call useState as many times as you like; each call is independent. Many developers prefer several small states over one big object, because updating one field never touches the others:

const [first, setFirst] = useState('');
const [last, setLast] = useState('');
const [subscribed, setSubscribed] = useState(true);

Expected output: typing in the first field updates only first; last and subscribed keep their values and do not cause extra work for siblings. When values genuinely belong together (a form), one object state is fine — spread the old object and replace the changed key.

Common mistakes

  • Mutating state directly — tags.push(...) or user.name = 'Ravi' will not re-render; always build a new array or object.
  • Calling the setter during render — update state inside event handlers or effects, not in the component body itself.
  • Expecting the variable to change immediately — count keeps its old value until the next render; that is why logging right after the setter shows the old number.
  • Deriving one state from another by hand — if a value can be computed from existing state during render, compute it instead of storing a copy that can drift out of sync.
  • Forgetting the import — useState must be imported from 'react' or you get a ReferenceError.

Practice checklist

  1. Build the counter above and verify the expected outputs for zero, one and two clicks.
  2. Add a "Decrement" button and a "Reset" button to the same component.
  3. Create a toggle with const [on, setOn] = useState(false) that switches a paragraph between "On" and "Off".
  4. Hold an array of strings in state; add and remove items using spread and filter, never mutation.
  5. Prove the batching rule: call the same setter twice in one click handler and observe the result.

State changes need a trigger — next you learn how to wire them to the user in Handling Events in React.

Quiz: State with useState

5 questions · 70% to pass

Frequently Asked Questions

React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.

You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.

React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.

A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.

JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.

Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.

Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.

An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.

Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.

React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.