React Essentials — Components, JSX, Props, State & MoreEntry 7 of 11

Managing State with useState

A hook for managing data that, when updated, tells React to re-execute the component function.

react.devuseState

Notes

const [counter, setCounter] = useState(0);

useState has three parts:

  • Current state value (counter): provided by React. It may change when the component function executes again.
  • State updating function (setCounter): updates the stored value and tells React to re-execute the component function in which useState was called.
  • Initial state value (0): stored by React and used only on the first render.

State updates lead to new state values, which appear when the component function executes again. The state variable is a snapshot of the current render, so right after calling the setter, it still holds the old value.

When the new value depends on the old one, pass an updater function (prev => prev + 1). For objects and arrays, always create a new value rather than mutating the existing one, or React won't detect the change.

What / Why / When

What

Local, reactive component state.

Why

Plain variables reset every render and don't trigger UI updates.

When

Any value that changes over time and affects what's rendered.

Use case

A selected tab, a toggle, form input values, a counter.

Code compare

React
import { useState } from "react";​type Todo = { id: number; text: string };​export function Counter() {  const [counter, setCounter] = useState(0);  const [todos, setTodos] = useState<Todo[]>([]);​  function increment() {    setCounter((prev) => prev + 1); // updater: safe when based on previous state    console.log(counter); // still the OLD value until the next render  }​  function addTodo(text: string) {    setTodos((prev) => [...prev, { id: Date.now(), text }]); // new array, no mutation  }​  return <button onClick={increment}>Clicked {counter} times</button>;}
Angular
import { Component, computed, signal } from "@angular/core";​type Todo = { id: number; text: string };​@Component({  selector: "app-counter",  template: `<button (click)="increment()">Clicked {{ counter() }} times</button>`,})export class CounterComponent {  counter = signal(0);  todos = signal<Todo[]>([]);  doubled = computed(() => this.counter() * 2); // derived state​  increment() {    this.counter.update((prev) => prev + 1);    console.log(this.counter()); // already the NEW value  }​  addTodo(text: string) {    this.todos.update((prev) => [...prev, { id: Date.now(), text }]);  }}

Differences and gotchas

  • React re-executes the entire component function on every state change. Angular never re-runs the class; it updates only the template bindings that read the changed signal.
  • A signal is read by calling it: counter(). React state is a plain variable.
  • A React setter doesn't change the value until the next render. signal.set() takes effect immediately.
  • Derived values: in React, compute them directly in the render body (or useMemo if expensive). In Angular, use computed().

Quick reference

ReactAngular
const [x, setX] = useState(0)x = signal(0)
xx()
setX(5)x.set(5)
setX(p => p + 1)x.update(p => p + 1)
Compute in render / useMemocomputed()