State with useState

Props let parents pass data down. But what about data that changes inside a component? That's what state is for. State is local, mutable data owned by a component.

The useState Hook

useState is the most-used React hook. It returns two things: the current value, and a function to update it.

counter.jsx
import { useState } from 'react';

function Counter() {
  // useState(0) sets initial value to 0
  // 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>
  );
}

How State Works

When you call the setter function (setCount), React:

  1. Stores the new state value
  2. Re-renders the component with the new value
  3. Updates the DOM with only what changed
??

Never mutate state directly! Always use the setter function. Doing count++ instead of setCount(count + 1) won't trigger a re-render.

State Can Hold Anything

State can be a number, string, boolean, array, or object:

types.jsx
import { useState } from 'react';

function Examples() {
  const [count, setCount] = useState(0);            // number
  const [name, setName] = useState("Alex");          // string
  const [isVisible, setIsVisible] = useState(true); // boolean
  const [items, setItems] = useState([]);            // array
  const [user, setUser] = useState({ name: "", age: 0 }); // object

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        {isVisible ? "Hide" : "Show"}
      </button>
      {isVisible && <p>Now you see me!</p>}
    </div>
  );
}

Updating Object State

When state is an object, you must spread the previous state to preserve other fields:

objects.jsx
import { useState } from 'react';

function UserForm() {
  const [user, setUser] = useState({ name: "", email: "", age: 0 });

  function updateName(newName) {
    // Spread operator preserves other fields
    setUser({ ...user, name: newName });
    // Without spread: setUser({ name: newName })
    // That would erase email and age!
  }

  return (
    <div>
      <input value={user.name} onChange={e => updateName(e.target.value)} />
      <p>Name: {user.name}, Email: {user.email}</p>
    </div>
  );
}

Multiple State Variables

Use multiple useState calls — one for each independent piece of state:

multiple.jsx
import { useState } from 'react';

function SearchBar() {
  const [query, setQuery] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [results, setResults] = useState([]);
  const [error, setError] = useState(null);

  async function search() {
    setIsLoading(true);
    setError(null);
    try {
      const data = await fetchResults(query);
      setResults(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsLoading(false);
    }
  }

  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <button onClick={search} disabled={isLoading}>
        {isLoading ? "Searching..." : "Search"}
      </button>
      {error && <p className="error">{error}</p>}
      {results.map(r => <div key={r.id}>{r.title}</div>)}
    </div>
  );
}
?

Keep related data in one state object, and unrelated data in separate useState calls.