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.
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 whichuseStatewas 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
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>;}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
useMemoif expensive). In Angular, usecomputed().
Quick reference
| React | Angular |
|---|---|
const [x, setX] = useState(0) | x = signal(0) |
x | x() |
setX(5) | x.set(5) |
setX(p => p + 1) | x.update(p => p + 1) |
Compute in render / useMemo | computed() |