Implementar la subida de archivos con arrastrar y soltar en React
La funcionalidad de subida de archivos con arrastrar y soltar mejora la experiencia de usuario al ofrecer una forma intuitiva de manejar archivos en aplicaciones web. En este artículo, usamos la biblioteca react-dropzone, que cuenta con amplio soporte, para construir un componente de subida de archivos robusto y con tipado seguro en React.
Configurar el proyecto
Usa un proyecto de React con TypeScript ya existente que tenga un bundler. Habilita allowImportingTsExtensions y
noEmit en su configuración de TypeScript para las importaciones explícitas de .ts y .tsx
que aparecen abajo; el bundler se encarga de la salida de JavaScript.
Comienza instalando la dependencia necesaria con npm o yarn:
npm install react-dropzone
# Or
yarn add react-dropzone
Crear un componente de subida de archivos con TypeScript
A continuación se muestra un ejemplo completo de un componente de subida de archivos con arrastrar y
soltar implementado con React y TypeScript. Este componente aprovecha react-dropzone para gestionar
la selección de archivos, la validación y el manejo de errores. Guárdalo como DragAndDropUpload.tsx. El
callback onFilesSelected informa de las selecciones aceptadas, no de subidas de red completadas;
react-dropzone no sube los archivos por sí mismo.
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
Manejar las subidas de archivos
Esta función muestra cómo manejar las subidas de archivos con la Fetch API junto con un manejo de
errores adecuado. Convierte los archivos seleccionados en un objeto FormData y los envía al endpoint
de tu API de back-end. Guarda la exportación nombrada de abajo en uploadFiles.ts. Este ejemplo espera
que /api/upload acepte campos multipart repetidos llamados files y devuelva un estado 2xx
después de aceptarlos. Los filtros de tipo y tamaño del lado del cliente son comprobaciones de
usabilidad; valida de nuevo el contenido, el tamaño y la autorización en el 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})`)
}
}
Aplicar estilos al componente
Los siguientes estilos CSS ayudan a crear un diseño limpio y adaptable para el componente de subida. Ajusta estos estilos según las necesidades de tu aplicación.
.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;
}
Subidas de archivos avanzadas con Uppy
Para proyectos que requieren funciones avanzadas como el seguimiento del progreso de subida, las subidas por fragmentos o las subidas reanudables, Uppy es una excelente alternativa. El fragmento de código de abajo muestra cómo integrar Uppy (versión 4.x) con React. Este fragmento solo renderiza el panel de selección. Agrega un plugin de subida como Tus, con su endpoint de servidor, para transferir los archivos. Usa versiones coincidentes de los paquetes de Uppy en un proyecto de Uppy 4 ya 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
Uppy se basa en estándares web modernos y funciona a la perfección con React, lo que lo convierte en una opción potente para manejar subidas de archivos complejas.
Consideraciones de accesibilidad y para móviles
Al implementar subidas de archivos con arrastrar y soltar, ten en cuenta lo siguiente para garantizar una gran experiencia de usuario en todos los dispositivos:
- Asegúrate de que la zona de arrastrar y soltar sea accesible desde el teclado añadiendo estilos de foco o atributos si es necesario.
- Usa instrucciones claras y concisas para que los usuarios entiendan cómo interactuar con el componente.
- Prueba la zona de arrastrar y soltar en dispositivos móviles para verificar que los comportamientos alternativos (como la selección de archivos mediante toques) funcionen de forma fiable.
- Considera añadir atributos ARIA, como
aria-label, para mejorar la accesibilidad para los lectores de pantalla.
Ejemplo de uso
Integra el componente DragAndDropUpload en tu aplicación como se muestra abajo. En este ejemplo, el
componente padre maneja las subidas de archivos invocando la función uploadFiles cuando finaliza la
selección de archivos.
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
Conclusión
En este artículo, construimos un componente de subida de archivos con arrastrar y soltar robusto y con tipado seguro usando react-dropzone. Vimos cómo configurar el proyecto, implementar la validación de archivos y el manejo de errores, y aplicar estilos al componente. Además, exploramos cómo se puede integrar Uppy para obtener funciones de subida avanzadas y ofrecimos consejos para garantizar la accesibilidad y la compatibilidad con móviles.
Para funciones más avanzadas como el seguimiento del progreso de subida, las subidas por fragmentos y las subidas reanudables, considera usar Uppy, un potente gestor de subida de archivos de código abierto desarrollado por Transloadit (https://uppy.io).
