cover

🚀 Subidas de Archivos Modernas en React: El Poder de un Contexto Global y Effect


Escuchar este post

Selecciona una voz y genera audio para escuchar este post

¿Alguna vez has intentado subir archivos en una app web y te has topado con problemas como bloqueos de la interfaz, subidas que se interrumpen si navegas a otra página, o la imposibilidad de ver el progreso real de la subida?
¡No estás solo! Subir archivos de forma robusta y amigable para el usuario es uno de los retos más subestimados en el desarrollo web moderno.

Hoy te cuento cómo resolvimos este reto en Easybits usando un contexto global de subidas y la librería Effect. Si eres principiante, ¡no te preocupes! Aquí te lo explico con ejemplos y analogías sencillas.


El Problema: Subidas “frágiles” y una mala experiencia

Imagina que tu usuario sube un archivo grande y, mientras tanto, decide navegar a otra sección de la app.
En una arquitectura tradicional, esto puede pasar:

  • La subida se cancela porque el componente se desmonta.
  • El usuario no sabe si la subida sigue, falló o terminó.
  • Si hay un error, no hay forma fácil de reintentar o cancelar.

¿Resultado? Frustración y una experiencia poco profesional.


La Solución: Un Contexto Global y Effect

1. Contexto Global de Subidas (UploadsContext)

Piensa en el contexto como una “central de control” para todas las subidas de archivos en tu app.
No importa en qué página estés, el contexto sabe:

  • Qué archivos se están subiendo.
  • El progreso de cada uno.
  • Si hubo errores, si se completó o si se canceló.

Esto permite que, aunque navegues entre páginas, la subida siga y puedas ver el progreso desde cualquier parte.

2. Effect: El “motor” de las tareas asíncronas

Effect es una librería que te ayuda a manejar tareas asíncronas (como subir archivos) de forma declarativa y segura.
¿Ventajas?

  • Puedes lanzar subidas en segundo plano sin bloquear la UI.
  • Puedes cancelar, reintentar o esperar el resultado de una tarea fácilmente.
  • El código es más fácil de leer y mantener.

¿Cómo se ve esto en código?

Envolviendo tu app con el provider:

<UploadsProvider> <App /> </UploadsProvider>

Usando el hook en cualquier componente:

import { useUploads } from "~/context"; const { uploads, uploadFile } = useUploads(); // Subir un archivo <input type="file" onChange={e => { const file = e.target.files?.[0]; if (file) uploadFile(file, "assetId"); }} /> // Mostrar progreso uploads.map(u => ( <div key={u.id}>{u.file.name}: {u.progress}%</div> ));

Beneficios para principiantes (¡y para todos!)

  • UX profesional: El usuario ve el progreso real y puede navegar sin miedo a perder la subida.
  • Control total: Puedes cancelar, reintentar o limpiar subidas desde cualquier parte de la app.
  • Reusabilidad: El mismo hook sirve para cualquier componente, en cualquier página.
  • Robustez: Si algo falla, el contexto lo sabe y puedes reaccionar (mostrar error, reintentar, etc.).
  • Escalabilidad: Puedes manejar muchas subidas a la vez, sin enredar tu código.

¿Por qué Effect y no solo Promesas?

Las promesas son geniales, pero cuando tienes muchas tareas asíncronas, cancelaciones, reintentos y dependencias, el código se vuelve difícil de manejar.
Effect te da una forma declarativa y segura de componer y controlar estos “efectos secundarios”, como si tuvieras superpoderes para manejar la complejidad.


¿Cómo se usa Effect dentro del Provider?

Dentro del UploadsProvider, Effect se encarga de manejar la lógica asíncrona de cada subida.
Esto significa que cada vez que llamas a uploadFile, Effect lanza la tarea de subida en “segundo plano”, actualiza el progreso y maneja los errores o cancelaciones.

El tipo que usaremos:

export interface UploadTask { id: string; file: File; assetId: string; progress: number; status: UploadStatus; error?: any; result?: any; abortController: AbortController; fiber?: any; // Effect Fiber }

Snippet clave dentro del Provider

import React, { createContext, useContext, useState } from "react"; import { Effect } from "effect"; import { useUploadMultipart } from "react-hook-multipart/react"; const UploadsContext = createContext<UploadsContextType | undefined>(undefined); export const UploadsProvider = ({ children }) => { const [uploads, setUploads] = useState<UploadTask[]>([]); // yo escribí esta biblioteca pero, puedes mejor usar uppy const { upload } = useUploadMultipart(); const uploadFile = (file, assetId) => { // ...crea el task y lo agrega al estado const effect = Effect.tryPromise({ try: async () => { // Llama a la función upload de react-hook-multipart const result = await upload( file.name, file, ({ percentage }) => { // Actualiza el progreso en el estado global setUploads((prev) => prev.map((t) => t.id === id ? { ...t, progress: percentage, status: "uploading" } : t ) ); }, { data: { assetId }, signal: abortController.signal } ); return result; }, catch: (error) => error, }).pipe( Effect.tap((result) => { // Marca como éxito al terminar setUploads((prev) => prev.map((t) => t.id === id ? { ...t, status: "success", result, progress: 100 } : t ) ); return Effect.unit; }), Effect.catchAll((error) => { // Marca como error si falla setUploads((prev) => prev.map((t) => t.id === id ? { ...t, status: "error", error } : t ) ); return Effect.unit; }) ); // Ejecuta el efecto (la subida) en segundo plano Effect.runPromise(effect); }; // ...algunas funciones omitidas return ( <UploadsContext.Provider value={{ uploads, uploadFile, cancelUpload, retryUpload, clearUpload }} > {children} </UploadsContext.Provider> ); }; // hook export const useUploads = () => { const ctx = useContext(UploadsContext); if (!ctx) throw new Error("useUploads must be used within an UploadsProvider"); return ctx; };

¿Qué hace este snippet?

  • Effect.tryPromise: Encapsula la lógica asíncrona de la subida (puede fallar, completarse o ser cancelada).
  • Effect.tap y Effect.catchAll: Permiten actualizar el estado global según el resultado (éxito o error).
  • Effect.runPromise: Lanza la tarea en segundo plano, sin bloquear la UI.
  • Callback de progreso: Actualiza el porcentaje de subida en tiempo real.

En resumen:
Effect te permite manejar subidas concurrentes, errores y cancelaciones de forma declarativa y centralizada, haciendo tu código más limpio y robusto.

Conclusión

Si quieres que tus usuarios tengan una experiencia de subida de archivos moderna, fluida y profesional,
¡usa un contexto global y Effect!
Tu código será más limpio, tu app más robusta y tus usuarios mucho más felices.

Abrazo. Bliss. 🤓

meta cover

Pseudo-introducción a Sveltekit para mi mismo.

Checa este otro Post

meta cover

How to get google one tap g_csrf_token cookie on Remix (simplest way)

Checa este otro Post

¡Nuevo curso!

Animaciones web con React + Motion 🧙🏻