Skip to main content

Documentation Index

Fetch the complete documentation index at: https://js.maxbraglia.com/llms.txt

Use this file to discover all available pages before exploring further.

Always confirm first

Deletion is the one operation you can’t undo. Always ask the user to confirm before deleting.
@app.delete("/api/users/{user_id}", status_code=204)
def delete_user(user_id: int):
    user = find_user(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    users.remove(user)
    # Returns nothing (204 No Content)

Simple confirmation with window.confirm

The quickest approach — a browser-native confirmation dialog:
function UserCard({ user, onDelete }) {
  const [deleting, setDeleting] = useState(false);

  async function handleDelete() {
    if (!window.confirm(`Delete ${user.name}? This can't be undone.`)) {
      return; // User clicked Cancel
    }

    setDeleting(true);
    try {
      await deleteUser(user.id);
      onDelete(user.id);
    } catch (err) {
      alert(`Failed to delete: ${err.message}`);
    } finally {
      setDeleting(false);
    }
  }

  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={handleDelete} disabled={deleting}>
        {deleting ? "Deleting..." : "Delete"}
      </button>
    </div>
  );
}
window.confirm() is perfectly fine for admin tools and internal apps. It’s ugly but it works.

Custom confirmation UI

For a better user experience, build your own confirmation:
function UserCard({ user, onDelete }) {
  const [confirming, setConfirming] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const [error, setError] = useState(null);

  async function handleConfirmedDelete() {
    setDeleting(true);
    setError(null);

    try {
      await deleteUser(user.id);
      onDelete(user.id);
    } catch (err) {
      setError(err.message);
      setConfirming(false);
    } finally {
      setDeleting(false);
    }
  }

  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>

      {error && <p className="error">{error}</p>}

      {confirming ? (
        <div className="confirm-delete">
          <p>Delete {user.name}?</p>
          <button
            onClick={handleConfirmedDelete}
            disabled={deleting}
            className="btn-danger"
          >
            {deleting ? "Deleting..." : "Yes, delete"}
          </button>
          <button onClick={() => setConfirming(false)}>
            Cancel
          </button>
        </div>
      ) : (
        <button onClick={() => setConfirming(true)}>
          Delete
        </button>
      )}
    </div>
  );
}
Two states: confirming (are we showing the confirmation?) and deleting (is the API call in progress?). The user clicks Delete → sees “Yes, delete” / “Cancel” → confirms → item is removed.

Updating the parent’s state

The parent removes the deleted item from its list:
function UserDashboard() {
  const [users, setUsers] = useState([]);

  function handleDelete(userId) {
    setUsers(prev => prev.filter(u => u.id !== userId));
  }

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}
.filter() creates a new array without the deleted item. This is the standard pattern for removing items from a list in React.
prev.filter(u => u.id !== userId) keeps every user whose ID does NOT match the deleted one. Simple, immutable, and correct. You’ll use this pattern alongside .map() for updates and [...prev, newItem] for creates.

The three state update patterns

You now have all the CRUD state updates:
// CREATE — add to the end
setUsers(prev => [...prev, newUser]);

// UPDATE — replace the matching item
setUsers(prev => prev.map(u => u.id === updated.id ? updated : u));

// DELETE — remove the matching item
setUsers(prev => prev.filter(u => u.id !== userId));
These three lines are the entire state management for a CRUD application. Memorize them — you’ll write them in every project.
All three patterns create new arrays instead of modifying the existing one. This is immutable state updates — React requires this to detect changes and re-render. Never use .push(), .splice(), or direct assignment on state arrays.

What’s next?

You’ve built all four CRUD operations. Now let’s see the complete picture — a full example with everything wired together from backend to frontend.

Full example walkthrough

See a complete CRUD application with React and FastAPI working together