Aprenda / Frontend — Tailwind & Next.js

IntermediárioAula 18 de 2015 min0% da trilha

Upload de arquivos e imagens

Um <input type="file"> sozinho não diz se o usuário selecionou o arquivo certo — preview e feedback fazem parte do upload.

Um <input type="file"> sozinho não dá feedback nenhum sobre o que foi selecionado — o usuário clica, escolhe um arquivo, e não tem confirmação visual de que pegou o certo. Um fluxo de upload de verdade mostra uma prévia antes de enviar e comunica erro quando o arquivo não serve.

Input file — capturando o arquivo selecionado

Lendo o arquivo do evento
"use client";

function InputComprovante({ onSelecionar }: { onSelecionar: (file: File) => void }) {
  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (file) onSelecionar(file);
  }

  return <input type="file" accept="image/*" onChange={handleChange} />;
}

Preview antes de enviar

Pra imagem, URL.createObjectURL(file) gera uma URL temporária, local, que aponta direto pro arquivo já selecionado no navegador — sem precisar enviar nada pro servidor só pra mostrar a prévia. O cuidado: essa URL precisa ser liberada com URL.revokeObjectURL quando não for mais usada, senão ela fica ocupando memória.

preview-comprovante.tsx
"use client";

import { useEffect, useState } from "react";

export function PreviewComprovante({ file }: { file: File | null }) {
  const [preview, setPreview] = useState<string | null>(null);

  useEffect(() => {
    if (!file) return;
    const url = URL.createObjectURL(file);
    setPreview(url);
    return () => URL.revokeObjectURL(url); // libera a memória ao trocar/desmontar
  }, [file]);

  if (!preview) return null;
  return <img src={preview} alt="Prévia do comprovante" className="w-40 h-40 object-cover rounded-lg" />;
}

Enviando com FormData

Arquivos não vão dentro de um JSON.stringify — eles vão num FormData. E não é preciso definir o header Content-Type manualmente: o navegador já define multipart/form-data com o boundary correto sozinho quando o corpo é um FormData.

Enviando o arquivo
async function enviarComprovante(file: File, pedidoId: string) {
  const formData = new FormData();
  formData.append("arquivo", file);
  formData.append("pedidoId", pedidoId);

  await fetch("/api/comprovantes", {
    method: "POST",
    body: formData, // sem Content-Type manual — o navegador cuida disso
  });
}

Exercício

Adicione validação em InputComprovante: rejeite arquivos maiores que 5MB ou que não sejam do tipo image/*, mostrando uma mensagem de erro em vez de aceitar o arquivo.

Quiz rápido

Por que usar URL.createObjectURL(file) pra mostrar uma preview antes de enviar a imagem?
Ao montar um FormData com um arquivo e enviar via fetch, por que normalmente não se define o header Content-Type manualmente?