ReReact · Lesson 3 of 8

State & useState

State is a component's memory. Unlike props (which come from outside), state lives inside the component and triggers a re-render when it changes. `useState` is the hook you'll use constantly.

JSX
import { useState } from 'react'

function Counter() {
  // useState returns [currentValue, setterFunction]
  const [count, setCount] = useState(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>
  )
}

// When state depends on previous value, use the updater form
function SafeCounter() {
  const [count, setCount] = useState(0)

  const increment = () => setCount(prev => prev + 1)
  // Always safe, even with multiple rapid updates
  return <button onClick={increment}>{count}</button>
}
JSX
import { useState } from 'react'

// State can be any value: string, number, object, array
function ProfileEditor() {
  const [user, setUser] = useState({
    name: "Alice",
    email: "alice@example.com",
    bio: "",
  })

  // Always create new objects/arrays — never mutate state directly
  const updateField = (field, value) => {
    setUser(prev => ({ ...prev, [field]: value }))
  }

  return (
    <form>
      <input
        value={user.name}
        onChange={e => updateField("name", e.target.value)}
        placeholder="Name"
      />
      <input
        value={user.email}
        onChange={e => updateField("email", e.target.value)}
        placeholder="Email"
      />
      <textarea
        value={user.bio}
        onChange={e => updateField("bio", e.target.value)}
        placeholder="Bio"
      />
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </form>
  )
}

// Array state — add, remove, update items
function TodoApp() {
  const [todos, setTodos] = useState([])
  const [text, setText] = useState("")

  const add    = () => {
    if (!text.trim()) return
    setTodos(prev => [...prev, { id: Date.now(), text, done: false }])
    setText("")
  }
  const toggle = (id) => setTodos(prev =>
    prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
  )
  const remove = (id) => setTodos(prev => prev.filter(t => t.id !== id))

  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)}
             onKeyDown={e => e.key === "Enter" && add()} />
      <button onClick={add}>Add</button>
      {todos.map(t => (
        <div key={t.id}>
          <input type="checkbox" checked={t.done} onChange={() => toggle(t.id)} />
          <span style={{ textDecoration: t.done ? "line-through" : "none" }}>{t.text}</span>
          <button onClick={() => remove(t.id)}>✕</button>
        </div>
      ))}
    </div>
  )
}
⚠ Warning
Never mutate state directly: `state.count++` or `state.items.push(x)`. React uses reference equality to detect changes — mutating the existing object means React sees the same reference and skips the re-render. Always create new objects/arrays: `setState(prev => ({ ...prev, count: prev.count + 1 }))`.