React State

React State

State is data that can change over time and, when it changes, causes the component to re-render with the new data. State is private โ€” it belongs to the component that declares it.

useState Basics

import { useState } from "react";

function Counter() {
  // useState(initialValue) 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>
  );
}

State vs Props

Props State
Source Passed from parent Owned by component
Mutable? No โ€” read only Yes โ€” via setter
Triggers re-render? Yes (when parent re-renders) Yes (when set)
Visible to parent? Yes No (unless lifted up)

Updating Object State

function ProfileForm() {
  const [profile, setProfile] = useState({
    name:  "",
    email: "",
    bio:   "",
  });

  const handleChange = (e) => {
    // ALWAYS spread previous state โ€” never mutate directly
    setProfile(prev => ({
      ...prev,
      [e.target.name]: e.target.value,  // Computed property name
    }));
  };

  return (
    <form>
      <input name="name"  value={profile.name}  onChange={handleChange} />
      <input name="email" value={profile.email} onChange={handleChange} />
      <textarea name="bio" value={profile.bio}  onChange={handleChange} />
    </form>
  );
}

Updating Array State

function TodoList() {
  const [todos, setTodos] = useState([]);

  // Add โ€” spread + new item
  const add = (text) =>
    setTodos(prev => [...prev, { id: Date.now(), text, done: false }]);

  // Remove โ€” filter out
  const remove = (id) =>
    setTodos(prev => prev.filter(t => t.id !== id));

  // Toggle โ€” map + spread
  const toggle = (id) =>
    setTodos(prev => prev.map(t =>
      t.id === id ? { ...t, done: !t.done } : t
    ));

  // Clear all
  const clear = () => setTodos([]);
}

Lifting State Up

When two sibling components need to share state, lift it to their nearest common ancestor and pass it down via props.

function App() {
  // State lives here โ€” shared between SearchBar and ResultsList
  const [query, setQuery] = useState("");

  return (
    <>
      <SearchBar query={query} onQueryChange={setQuery} />
      <ResultsList query={query} />
    </>
  );
}

function SearchBar({ query, onQueryChange }) {
  return <input value={query} onChange={e => onQueryChange(e.target.value)} />;
}
WarningNever modify state directly: todos.push(item) โŒ. Always use the setter: setTodos([...todos, item]) โœ…. Direct mutation does not trigger a re-render.