Creating Products from a Form

Form with Server-Side Errors

The form posts JSON and shows the field errors from your ProblemDetail response (the errors map from the Validation module) next to each input.

// src/pages/AddProduct.jsx
import { useState } from 'react';
import { createProduct } from '../api/products';

const empty = { sku: '', name: '', description: '', price: '', stock: 0, category: '' };

export default function AddProduct({ onCreated }) {
  const [form, setForm] = useState(empty);
  const [errors, setErrors] = useState({});

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

  async function submit(e) {
    e.preventDefault();
    try {
      const saved = await createProduct({ ...form, price: Number(form.price), stock: Number(form.stock) });
      setForm(empty);
      setErrors({});
      onCreated(saved);
    } catch (problem) {
      setErrors(problem.errors || { form: problem.detail });
    }
  }

  return (
    <form onSubmit={submit}>
      {['sku', 'name', 'description', 'price', 'stock', 'category'].map((field) => (
        <label key={field}>
          {field}
          <input name={field} value={form[field]} onChange={change} />
          {errors[field] && <small className="error">{errors[field]}</small>}
        </label>
      ))}
      {errors.form && <p className="error">{errors.form}</p>}
      <button type="submit">Add product</button>
    </form>
  );
}

What Travels over the Wire

Example

Seen in the browser Network tab when the user leaves the name empty:

POST http://localhost:8080/api/products
Origin: http://localhost:5173
Content-Type: application/json

{"sku":"ELE-1003","name":"","description":"","price":0,"stock":10,"category":"Electronics"}

Example output

HTTP/1.1 400 Bad Request
Access-Control-Allow-Origin: http://localhost:5173
Content-Type: application/problem+json

{"title":"Validation failed","status":400,"detail":"Request has invalid fields","errors":{"name":"must not be blank","price":"must be greater than or equal to 0.01"}}

Exercise: After a successful create, the list page should show the new product without reloading. How do you connect AddProduct and ProductList?

Show answer
// Lift the products state to a common parent and pass a callback down:
function ProductsPage() {
  const [products, setProducts] = useState([]);
  // ...load with getProducts() in useEffect...
  return (
    <>
      <AddProduct onCreated={(p) => setProducts((list) => [...list, p])} />
      <ProductTable products={products} />
    </>
  );
}