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:
- Collects all named form fields into a
FormDataobject - 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.
Navigation After Upload
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:
- You want the simplest possible implementation
- The file input’s native browser UI is acceptable
- You don’t need a preview before uploading
For drag-and-drop support and an image preview, see the next page.