Implementando upload de arquivos com arrastar e soltar no React
O upload de arquivos com arrastar e soltar melhora a experiência do usuário ao oferecer uma forma intuitiva de lidar com arquivos em aplicações web. Neste post, usamos a biblioteca react-dropzone, que tem bom suporte, para criar um componente de upload de arquivos robusto e com tipagem segura em React.
Configurando o projeto
Use um projeto React com TypeScript já existente que tenha um bundler. Ative
allowImportingTsExtensions e noEmit na configuração do TypeScript para
os imports explícitos de .ts e .tsx abaixo; o
bundler cuida da saída em JavaScript.
Comece instalando a dependência necessária com npm ou yarn:
npm install react-dropzone
# Or
yarn add react-dropzone
Criando um componente de upload de arquivos em TypeScript
Veja abaixo um exemplo completo de um componente de upload de arquivos com arrastar e soltar,
implementado com React e TypeScript. Esse componente usa o react-dropzone para gerenciar a seleção
de arquivos, a validação e o tratamento de erros. Salve-o como DragAndDropUpload.tsx. O
callback onFilesSelected informa as seleções aceitas, não uploads concluídos pela
rede; o react-dropzone não faz o upload dos arquivos por conta própria.
import React, { useCallback, useState } from 'react'
import { useDropzone, type FileRejection } from 'react-dropzone'
interface UploadProps {
onFilesSelected?: (files: File[]) => void
maxFiles?: number
maxSize?: number
}
const DragAndDropUpload: React.FC<UploadProps> = ({
onFilesSelected,
maxFiles = 1,
maxSize = 5242880, // 5MB
}) => {
const [files, setFiles] = useState<File[]>([])
const [uploadError, setUploadError] = useState<string | null>(null)
const onDropAccepted = useCallback((acceptedFiles: File[]) => {
setFiles(acceptedFiles)
setUploadError(null)
if (onFilesSelected) {
onFilesSelected(acceptedFiles)
}
}, [onFilesSelected])
const onDropRejected = useCallback((fileRejections: FileRejection[]) => {
const errors = fileRejections.map(
(rejection) => `${rejection.file.name}: ${rejection.errors[0].message}`
)
setUploadError(errors.join('\n'))
}, [])
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDropAccepted,
onDropRejected,
maxFiles,
maxSize,
accept: {
'image/*': ['.jpeg', '.jpg', '.png', '.gif'],
'application/pdf': ['.pdf'],
},
})
return (
<div className="upload-container">
<div
{...getRootProps()}
className={`dropzone ${isDragActive ? 'active' : ''}`}
>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop the files here</p>
) : (
<p>Drag and drop files here, or click to select files</p>
)}
</div>
{uploadError && <div className="error-message">{uploadError}</div>}
{files.length > 0 && (
<div className="file-list">
<h4>Selected Files:</h4>
<ul>
{files.map((file) => (
<li key={file.name}>
{file.name} - {(file.size / 1024).toFixed(2)} KB
</li>
))}
</ul>
</div>
)}
</div>
)
}
export default DragAndDropUpload
Processando uploads de arquivos
Esta função mostra como processar uploads de arquivos usando a Fetch API com o tratamento de erros
adequado. Ela converte os arquivos selecionados em um objeto FormData e os envia para o endpoint da
sua API de back-end. Salve o export nomeado abaixo em uploadFiles.ts. Este exemplo
espera que /api/upload aceite campos multipart repetidos com o nome
files e retorne um status 2xx depois de aceitá-los. Os filtros de tipo e
tamanho no lado do cliente servem apenas para melhorar a usabilidade; valide novamente o conteúdo, o
tamanho e a autorização no servidor.
export async function uploadFiles(files: File[]): Promise<void> {
const formData = new FormData()
for (const file of files) {
formData.append('files', file)
}
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
if (!response.ok) {
throw new Error(`Upload failed (HTTP ${response.status})`)
}
}
Estilizando o componente
Os estilos CSS a seguir ajudam a criar um design limpo e responsivo para o componente de upload. Ajuste esses estilos conforme a necessidade da sua aplicação.
.upload-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.dropzone {
border: 2px dashed #0087f7;
border-radius: 4px;
padding: 20px;
text-align: center;
background: #f8f9fa;
cursor: pointer;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #00e676;
background: #e3f2fd;
}
.error-message {
color: #d32f2f;
margin-top: 10px;
padding: 10px;
background: #ffebee;
border-radius: 4px;
}
.file-list {
margin-top: 20px;
}
.file-list ul {
list-style: none;
padding: 0;
}
.file-list li {
padding: 8px;
background: #f5f5f5;
margin-bottom: 8px;
border-radius: 4px;
}
Uploads de arquivos avançados com Uppy
Para projetos que exigem recursos avançados, como acompanhamento do progresso de upload, uploads em partes (chunks) ou uploads retomáveis, o Uppy é uma excelente alternativa. O trecho abaixo mostra como integrar o Uppy (versão 4.x) ao React. Esse trecho renderiza apenas o painel de seleção. Adicione um plugin de upload, como o plugin Tus, com o endpoint do servidor correspondente, para transferir os arquivos. Use versões compatíveis dos pacotes do Uppy em um projeto Uppy 4 já existente.
import Uppy from '@uppy/core'
import { Dashboard } from '@uppy/react'
import '@uppy/core/dist/style.min.css'
import '@uppy/dashboard/dist/style.min.css'
const uppy = new Uppy()
function UppyUploader() {
return <Dashboard uppy={uppy} />
}
export default UppyUploader
O Uppy se baseia em padrões web modernos e funciona perfeitamente com React, o que o torna uma opção poderosa para lidar com uploads de arquivos complexos.
Acessibilidade e considerações para dispositivos móveis
Ao implementar uploads de arquivos com arrastar e soltar, leve em conta os pontos a seguir para garantir uma ótima experiência do usuário em todos os dispositivos:
- Garanta que a área de soltar seja acessível pelo teclado, adicionando estilos de foco ou atributos, se necessário.
- Use instruções claras e concisas para que os usuários entendam como interagir com o componente.
- Teste a área de soltar em dispositivos móveis para verificar se os comportamentos alternativos (como a seleção de arquivos por toque) funcionam de forma confiável.
- Considere adicionar atributos ARIA, como
aria-label, para melhorar a acessibilidade para leitores de tela.
Exemplo de uso
Integre o componente DragAndDropUpload à sua aplicação como mostrado abaixo. Neste exemplo, o
componente pai processa os uploads de arquivos chamando a função uploadFiles
quando a seleção de arquivos é concluída.
import React from 'react'
import DragAndDropUpload from './DragAndDropUpload.tsx'
import { uploadFiles } from './uploadFiles.ts'
const App: React.FC = () => {
const handleFilesSelected = async (files: File[]) => {
try {
await uploadFiles(files)
console.log('Upload completed')
} catch (error) {
console.error('Upload failed:', error)
}
}
return (
<div>
<h1>File Upload</h1>
<DragAndDropUpload
onFilesSelected={handleFilesSelected}
maxFiles={5}
maxSize={5242880}
/>
</div>
)
}
export default App
Conclusão
Neste post, criamos um componente de upload de arquivos com arrastar e soltar robusto e com tipagem segura usando o react-dropzone. Vimos como configurar o projeto, implementar a validação de arquivos e o tratamento de erros, e estilizar o componente. Além disso, exploramos como o Uppy pode ser integrado para oferecer recursos de upload avançados e demos dicas para garantir acessibilidade e uma boa experiência em dispositivos móveis.
Para recursos mais avançados, como acompanhamento do progresso de upload, uploads em partes (chunks) e uploads retomáveis, considere usar o Uppy, uma ferramenta de upload de arquivos poderosa e de código aberto desenvolvida pela Transloadit (https://uppy.io).
