React + Next.js

State, Events and Forms

35 min

State

State is data that changes over time and should update the UI when it does.

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Clicked {count} times
    </button>
  );
}
  • Calling the setter re-renders the component with the new value.
  • Use the function form setCount(c => c + 1) when the new value depends on the old one.
  • Never mutate state directly: items.push(x) does not re-render. Create new arrays and objects: setItems([...items, x]).

Controlled forms

function ContactForm({ onSubmit }) {
  const [form, setForm] = useState({ name: "", email: "", message: "" });
  const [error, setError] = useState("");

  const update = e => setForm({ ...form, [e.target.name]: e.target.value });

  function handleSubmit(e) {
    e.preventDefault();
    if (form.message.length < 20) return setError("Please add more detail.");
    setError("");
    onSubmit(form);
  }

  return (
    <form onSubmit={handleSubmit} noValidate>
      <label>Name <input name="name" value={form.name} onChange={update} required /></label>
      <label>Email <input name="email" type="email" value={form.email} onChange={update} required /></label>
      <label>Message <textarea name="message" value={form.message} onChange={update} /></label>
      {error && <p role="alert">{error}</p>}
      <button>Send</button>
    </form>
  );
}

The input's value always comes from state, so validation and formatting are easy.

Lifting state up

When two components need the same data, keep the state in their closest common parent and pass it down as props, with callbacks to change it:

function Shop() {
  const [cart, setCart] = useState([]);
  const add = product => setCart(c => [...c, product]);
  return (<><ProductList onAdd={add} /><CartSummary items={cart} /></>);
}

Derived values

Do not store what you can calculate:

const total = cart.reduce((sum, p) => sum + p.cents, 0);   // computed on each render

Duplicate state gets out of sync; derived values cannot.

Exercise

Build a filterable project list: a search input and category buttons in state; the visible list is derived with filter; a counter shows "N projects found".

Check yourself

  • Why does items.push(x) fail to update the UI?
  • What is a controlled input?
  • Why calculate totals instead of storing them in state?

Your progress is saved in this browser only.

Submit for review (10 points)