Bart Dorsey

Simple Upload Form

SimpleUploadForm is the most straightforward approach to file uploads — a standard HTML file input wired up with React 19 form actions.


The Component

frontend/src/features/upload/SimpleUploadForm.tsx on GitHub

// src/features/upload/SimpleUploadForm.tsx
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { uploadPhoto } from "../../api/photos-api";

export default function SimpleUploadForm() {
    const [error, setError] = useState("");
    const navigate = useNavigate();

    async function handleSubmit(formData: FormData) {
        setError("");
        const file = formData.get("photo");

        if (file instanceof File) {
            if (
                file.type !== "image/jpeg" &&
                file.type !== "image/png" &&
                file.type !== "image/gif" &&
                file.type !== "image/webp"
            ) {
                setError("Unsupported image type");
                return;
            }

            const result = await uploadPhoto(formData);
            if (result.success) {
                navigate("/");
            } else {
                setError(result.error);
            }
        }
    }

    return (
        <div className="max-w-2xl mx-auto">
            <h2 className="text-2xl font-bold mb-6">Upload a Photo</h2>

            <form className="space-y-6" action={handleSubmit}>
                {error && (
                    <div className="bg-red-50 border border-red-200 rounded-lg p-4">
                        <p className="text-red-800 font-medium">{error}</p>
                    </div>
                )}

                <label className="block">
                    <span className="font-medium mb-2 block">Title (optional)</span>
                    <input
                        type="text"
                        name="title"
                        placeholder="Enter a title for your photo"
                        className="block w-full px-4 py-3 border border-gray-300 rounded-lg"
                    />
                </label>

                <label className="block">
                    <span className="font-medium mb-2 block">Choose Photo</span>
                    <input
                        type="file"
                        name="photo"
                        accept="image/jpeg,image/png,image/gif,image/webp"
                        className="block w-full"
                    />
                </label>

                <button type="submit" className="w-full py-3 px-6 bg-blue-600 text-white rounded-lg">
                    Upload Photo
                </button>
            </form>
        </div>
    );
}

Key Concepts

React 19 Form Actions

<form action={handleSubmit}>

Passing an async function to action is a React 19 feature. When the form is submitted, React automatically:

  1. Collects all named form fields into a FormData object
  2. Calls handleSubmit(formData) with it

No event.preventDefault() needed — React handles that. No manual new FormData(event.target) either.

Client-Side Validation

const file = formData.get("photo");

if (file instanceof File) {
    if (file.type !== "image/jpeg" && ...) {
        setError("Unsupported image type");
        return;
    }
}

The instanceof File check serves two purposes: it narrows the TypeScript type (since formData.get() returns string | File | null), and it confirms the user actually selected a file rather than leaving the input empty.

The MIME type check (file.type) gives immediate feedback without a network round-trip. This is not a security measure — the backend validates again. A user could set file.type to anything with the right tools, so the server never trusts the client’s type claim.

Error State

const [error, setError] = useState("");

A single string state handles both client-side validation errors and server errors. The API layer normalises server errors into the same { success: false, error: string } shape, so the component doesn’t need to know where the error came from.

if (result.success) {
    navigate("/");
}

After a successful upload, useNavigate from React Router sends the user back to the photo gallery. The new photo will appear because the gallery re-fetches on mount.


When to Use This Component

SimpleUploadForm is the right choice when:

For drag-and-drop support and an image preview, see the next page.