Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 56
Beginner

React useState Hook

Manage React component state with useState to store and update values across renders.

TL;DR

  1. 01Declare state with useState and get back value and setter.
  2. 02Call setter to update state, triggering a re-render.
  3. 03Use functional updates for state based on previous state.

Tips

  1. 01Use functional updates (prev => ...) when the new state depends on the old state — it's safer and more readable.

Warnings

  1. 01Never mutate state directly — always create new objects and arrays to trigger re-renders properly.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 57
Beginner

React useState Hook

(continued)

Basic useState

  • Declare state with initial value.
    const [count, setCount] = useState(0);
  • count is the current value, setCount updates and re-renders.
    function Counter() {
      const [count, setCount] = useState(0);
      
      return (
        <>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>
            Increment
          </button>
        </>
      );
    }
  • Each state variable needs its own useState call.
  • Initialize state with strings, numbers, booleans, arrays, or objects.
    const [name, setName] = useState("");
    const [isOpen, setIsOpen] = useState(false);
    const [items, setItems] = useState([]);
    const [user, setUser] = useState(null);
  • Calling the setter always replaces the value, never merges.
    // Calling setCount replaces the entire value
    setCount(42); // count is now 42, not 42 + oldCount
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 58
Beginner

React useState Hook

(continued)

Multiple State Variables

  • Declare multiple state variables separately.
    function Form() {
      const [name, setName] = useState("");
      const [email, setEmail] = useState("");
      const [age, setAge] = useState(0);
      
      return (
        <>
          <input value={name} onChange={(e) => setName(e.target.value)} />
          <input value={email} onChange={(e) => setEmail(e.target.value)} />
          <input value={age} onChange={(e) => setAge(e.target.value)} />
        </>
      );
    }
  • Group related state into a single object.
    const [form, setForm] = useState({
      name: "",
      email: "",
      age: 0
    });
  • Update a single field in an object with spread syntax.
    const handleChange = (field) => (e) => {
      setForm(prev => ({ ...prev, [field]: e.target.value }));
    };
  • Keep unrelated state variables separate for clarity.
    // These are unrelated — keep them separate
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null);
    const [data, setData] = useState(null);
  • Use useReducer when multiple variables always update together.
    // If isLoading, error, and data always change at once
    // consider useReducer instead of three useState calls
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 59
Beginner

React useState Hook

(continued)

Functional Updates

  • Use a function to update state based on previous value.
    function Counter() {
      const [count, setCount] = useState(0);
      
      // Direct update (fine for simple cases)
      // setCount(count + 1);
      
      // Functional update (preferred for dependent updates)
      return <button onClick={() => setCount(prev => prev + 1)}>Increment</button>;
    }
  • Functional updates are safer when multiple updates happen fast.
    const handleClick = () => {
      setCount(prev => prev + 1);
      setCount(prev => prev + 1);
      // Both increments use the latest state — count increases by 2
    };
  • Use functional updates inside useEffect and useCallback.
    useEffect(() => {
      const timer = setInterval(() => {
        setCount(prev => prev + 1); // safe inside interval
      }, 1000);
      return () => clearInterval(timer);
    }, []); // no count in deps needed
  • Functional updates work with arrays too.
    function addItem(newItem) {
      setItems(prev => [...prev, newItem]);
    }
    
    function removeItem(id) {
      setItems(prev => prev.filter(item => item.id !== id));
    }
  • Direct updates are fine when the new value doesn't depend on old state.
    // setName doesn't need the previous name
    setName("Alice");
    setIsOpen(true);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 60
Beginner

React useState Hook

(continued)

Lazy Initialization

  • Initialize state from an expensive computation using a function.
    const [state, setState] = useState(() => {
      return expensiveComputation();
    });
  • Function runs only on mount, not on every render.
    const [todos, setTodos] = useState(() => {
      const saved = localStorage.getItem("todos");
      return saved ? JSON.parse(saved) : [];
    });
  • Useful for loading from localStorage or parsing initial data.
  • Without lazy initialization, the function runs on every render.
    // Bad: expensiveComputation() runs on every render
    const [state, setState] = useState(expensiveComputation());
    
    // Good: arrow function defers the call to mount only
    const [state, setState] = useState(() => expensiveComputation());
  • Pass the function itself, not the result of calling it.
    // Correct: pass a function
    useState(() => computeInitialValue())
    
    // Wrong: calls the function immediately
    useState(computeInitialValue())
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 61
Beginner

React useState Hook

(continued)

Updating Objects and Arrays

  • Create a new object when updating — never mutate state directly.
    const [user, setUser] = useState({ name: "Alice", age: 30 });
    
    // Wrong: mutation — React won't detect the change
    user.age = 31;
    
    // Right: create new object with spread
    setUser({ ...user, age: 31 });
  • Create a new array for all array state operations.
    const [items, setItems] = useState(["a", "b", "c"]);
    
    // Add item
    setItems([...items, "d"]);
    
    // Remove item
    setItems(items.filter(item => item !== "b"));
  • Update a nested object by spreading at every level.
    const [profile, setProfile] = useState({ name: "Alice", address: { city: "NYC" } });
    
    // Update nested field
    setProfile(prev => ({
      ...prev,
      address: { ...prev.address, city: "LA" }
    }));
  • Update a specific item in an array by mapping over it.
    const [tasks, setTasks] = useState([{ id: 1, done: false }]);
    
    function toggleTask(id) {
      setTasks(prev =>
        prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
      );
    }
  • Use the Immer library for complex nested updates.
    import produce from "immer";
    
    setProfile(produce(draft => {
      draft.address.city = "LA"; // direct mutation is safe inside produce
    }));
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useState Hook
Chapter 08 · Page 62
Beginner

React useState Hook

(FAQ)

FAQ

Use functional updates whenever the new state depends on the previous state, especially inside event handlers, async callbacks, or effects where the closure may capture a stale value. This guarantees you're working with the latest state, not a snapshot from when the function was created.

Spread the existing object and override only the changed fields: setState(prev => ({ ...prev, name: 'new' })). React requires a new object reference to detect the change, so never do setState(obj.name = 'new') directly.

Yes — declare as many useState calls as you need, one per logical piece of state. Keeping state variables separate (e.g., const [name, setName] and const [age, setAge]) is cleaner and avoids the need to spread on every update compared to storing everything in one object.

Passing a function to useState — useState(() => expensiveCalc()) — tells React to call it only on the initial render instead of every render. Use this when the initial value requires heavy computation, parsing, or reading from localStorage.

Calling array.push() mutates the existing array, so React sees the same reference and skips the re-render. Instead, create a new array: setState(prev => [...prev, newItem]).