Файлове поле завжди неконтрольоване: задати йому значення з коду не можна, тож value не передають. Файли читають з події чи з FormData.
function AvatarForm() {
const [preview, setPreview] = useState(null);
useEffect(() => () => preview && URL.revokeObjectURL(preview), [preview]);
function onChange(e) {
const file = e.target.files?.[0];
if (!file) return;
if (file.size > 2 * 1024 * 1024) {
alert('Файл більший за 2 МБ');
e.target.value = ''; // скинути вибір
return;
}
setPreview(URL.createObjectURL(file));
}
async function upload(formData) {
await fetch('/api/avatar', { method: 'POST', body: formData, headers: { Accept: 'application/json' } });
}
return (
<form action={upload}>
<input type="file" name="avatar" accept="image/png,image/jpeg" onChange={onChange} />
{preview && <img src={preview} alt="Попередній перегляд" width={120} />}
<button>Завантажити</button>
</form>
);
}
Що тут важливо:
URL.createObjectURLстворює посилання на файл у пам'яті браузера. Його треба звільняти (revokeObjectURL) при заміні файлу й знищенні компонента, інакше пам'ять не звільняється до закриття вкладки;Content-Typeне задавати вручну дляFormData- браузер сам поставитьmultipart/form-dataз межею (boundary);acceptі перевірка розміру - лише зручність; сервер перевіряє тип за вмістом (image,mimes) і розмір заново;multiple-e.target.filesдає список; уFormData-formData.getAll('photos').
Прогрес завантаження. fetch не повідомляє про прогрес відправки тіла. Для смуги прогресу - XMLHttpRequest (xhr.upload.onprogress) чи бібліотека, що його використовує.
Великі файли (відео, архіви):
- завантаження частинами з продовженням після обриву;
- пряме завантаження в S3 за підписаним URL, отриманим від Laravel (
Storage::temporaryUploadUrl()): файл іде з браузера в сховище, минаючи сервер застосунку; - обмеження сервера (
upload_max_filesize,client_max_body_sizeу Nginx) мають відповідати очікуваним розмірам.
PUT/PATCH з файлами в Laravel: PHP історично розбирає multipart лише для POST - тому відправляють POST з полем _method=PUT.
Доступність і UX: кнопка вибору файлу має мітку, а для перетягування (drag and drop) лишається звичайне поле як запасний варіант.
Докладніше в документації: input: читання значень при відправці