10.6-Cómo evitar perder datos al desmontar componentes en React

 

Cómo evitar perder datos al desmontar componentes en React

Te explico las 4 estrategias principales con ejemplos prácticos:


⚠️ El Problema:

Cuando un componente se desmonta, todo su estado local se pierde:

function FormularioTemporal() {

  const [texto, setTexto] = useState(''); // ❌ Se pierde al desmontar

  const [contador, setContador] = useState(0); // ❌ Se pierde al desmontar

  

  return <input value={texto} onChange={e => setTexto(e.target.value)} />;

}


✅ Estrategia 1: Levantar el Estado (Lifting State Up)

Mover el estado al componente padre que NO se desmonta:

ANTES (problema):

function Hijo() {

  const [datoImportante, setDatoImportante] = useState(''); // ❌ Se pierde

  

  return <div>{datoImportante}</div>;

}


function Padre() {

  const [mostrarHijo, setMostrarHijo] = useState(true);

  

  return (

    <div>

      <button onClick={() => setMostrarHijo(!mostrarHijo)}>

        Mostrar/Ocultar

      </button>

      {mostrarHijo && <Hijo />} {/* Al ocultar, el estado se pierde */}

    </div>

  );}

DESPUÉS (solución):

jsx

function Hijo({ datoImportante, setDatoImportante }) {

  // El estado ahora viene del padre como prop

  return (

    <input 

      value={datoImportante}

      onChange={e => setDatoImportante(e.target.value)}

    />

  );

}


function Padre() {

  // El estado vive AQUÍ, donde no se desmonta

  const [datoImportante, setDatoImportante] = useState(''); // ✅ Persiste

  const [mostrarHijo, setMostrarHijo] = useState(true);

  

  return (

    <div>

      <button onClick={() => setMostrarHijo(!mostrarHijo)}>

        Mostrar/Ocultar

      </button>

      

      {/* Hijo se puede desmontar, pero el estado persiste en Padre */}

      {mostrarHijo && (

        <Hijo 

          datoImportante={datoImportante}

          setDatoImportante={setDatoImportante}

        />

      )}

      

      {/* El dato sigue disponible aunque Hijo esté oculto */}

      <p>Dato guardado: {datoImportante}</p>

    </div>

  );

}

Esto es exactamente lo que hace nuestro App.js con tasks. 🎯



✅ Estrategia 2: localStorage (persistencia en el navegador)

Guardar el estado en el almacenamiento del navegador:

jsx

function FormularioPersistente() {

  // 1. Cargar estado inicial desde localStorage

  const [texto, setTexto] = useState(() => {

    const guardado = localStorage.getItem('miTexto');

    return guardado || '';

  });

  

  // 2. Guardar en localStorage CADA VEZ que cambie

  useEffect(() => {

    localStorage.setItem('miTexto', texto);

  }, [texto]); // Se ejecuta cada vez que 'texto' cambia

  

  return (

    <input 

      value={texto}

      onChange={e => setTexto(e.target.value)}

      placeholder="Esto se guarda aunque cierres la pestaña"

    />

  );}

Versión mejorada para nuestro App.js de tareas:

function App() {

  // Estado inicial desde localStorage

  const [tasks, setTasks] = useState(() => {

    try {

      const guardado = localStorage.getItem('tareas');

      return guardado ? JSON.parse(guardado) : [];

    } catch {

      return [];

    }

  });

  

  // Auto-guardar cuando tasks cambia

  useEffect(() => {

    localStorage.setItem('tareas', JSON.stringify(tasks));

  }, [tasks]);

  

  // Resto del código igual...}

Ventajas:

  • Los datos sobreviven a recargas de página

  • Sobreviven a cerrar y abrir el navegador

  • Fácil de implementar













✅ Estrategia 3: Context API (Estado Global)

Compartir estado entre componentes sin pasar props manualmente:

jsx

// 1. Crear Contexto

const TasksContext = React.createContext();

// 2. Provider (proveedor) - El "banco de datos"

function TasksProvider({ children }) {

  const [tasks, setTasks] = useState([]);

  // El Provider NUNCA se desmonta (está en App)

  return (

    <TasksContext.Provider value={{ tasks, setTasks }}>

      {children}

    </TasksContext.Provider>

  );

}

// 3. Consumir en cualquier componente

function TaskForm() {

  const { setTasks } = useContext(TasksContext);

  const [textoLocal, setTextoLocal] = useState(''); // Estado temporal local

  const agregarTarea = () => {

    setTasks(tareasPrev => [...tareasPrev, { texto: textoLocal }]);

    setTextoLocal(''); // Limpiar input local

  };

  return (

    <div>

      <input 

        value={textoLocal}

        onChange={e => setTextoLocal(e.target.value)}

      />

      <button onClick={agregarTarea}>Agregar</button>

    </div>

  );

}

// 4. App principal

function App() {

  return (

    <TasksProvider> {/* Envuelve toda la app */}

      <TaskForm />   {/* Puede montarse/desmontarse */}

      <TaskList />   {/* Puede montarse/desmontarse */}

      <OtroComponente /> {/* Accede al mismo estado */}

    </TasksProvider>

  );}


✅ Estrategia 4: useRef (para valores que no causan re-render)

Para datos que necesitas conservar pero que no afectan la UI:

jsx

function FormularioConHistorial() {

  const [texto, setTexto] = useState('');

  const historial = useRef([]); // ⚡️ NO se pierde al desmontar

  

  // useRef persiste entre renders y NO desencadena re-renders

  

  const guardarEnHistorial = () => {

    historial.current.push({

      texto,

      fecha: new Date().toISOString()

    });

    console.log('Historial:', historial.current);

  };

  

  return (

    <div>

      <input 

        value={texto}

        onChange={e => setTexto(e.target.value)}

      />

      <button onClick={guardarEnHistorial}>Guardar</button>

    </div>

  );

}

Nota: useRef NO se limpia automáticamente. Persiste mientras el componente esté montado.



📊 Comparativa de métodos:

Método

Persiste en

Fácil

Rendimiento

Caso de uso

Levantar estado

Mientras padre esté montado

✅ Muy fácil

✅ Excelente

Datos compartidos entre pocos componentes

localStorage

Navegador (aunque cierre)

✅ Fácil

⚠️ Sincrónico (bloquea)

Preferencias de usuario, datos importantes

Context API

Mientras app esté montada

⚠️ Moderado

⚠️ Puede causar re-renders

Estado global compartido

useRef

Mismo componente

✅ Fácil

✅ Excelente

Valores que no afectan UI (timeouts, IDs)






🎯 Mejor solución para nuestro App.js de tareas:

La combinación ideal sería:

jsx

function App() {

  // 1. Estado en App (componente raíz que no se desmonta)

  const [tasks, setTasks] = useState(() => {

    // 2. Cargar desde localStorage para persistencia

    const saved = localStorage.getItem('tasks');

    return saved ? JSON.parse(saved) : [];

  });

  

  // 3. Auto-guardar en localStorage

  useEffect(() => {

    localStorage.setItem('tasks', JSON.stringify(tasks));

  }, [tasks]);

  

  // 4. Pasar a componentes hijos como props

  return (

    <div>

      <TaskForm onAddTask={addTask} />

      <TaskList tasks={tasks} />

      

      {/* Ejemplo: componente que puede montarse/desmontarse */}

      {showStats && <TaskStats tasks={tasks} />}

    </div>

  );

}

Con esta solución:

  • tasks vive en App (nunca se desmonta)

  • ✅ Se guarda en localStorage (sobrevive a recargas)

  • ✅ Los componentes hijos pueden montarse/desmontarse libremente

  • ✅ Los datos siempre están disponibles



🚨 Error común que evitar:

jsx

// ❌ MAL: Estado en componente que se desmonta

function PopupTemporal() {

  const [datosImportantes, setDatosImportantes] = useState({});

  // Cuando el popup se cierre, ¡adiós datos!

  

  return <div>...</div>;

}


// ✅ BIEN: 

// 1. Estado en componente superior

// 2. O guardar antes de desmontar

function PopupTemporal({ datos, onSave, onClose }) {

  const [datosLocales, setDatosLocales] = useState(datos);

  

  const cerrar = () => {

    onSave(datosLocales); // Guardar antes de cerrar

    onClose();

  };

  

  return (

    <div>

      <input value={datosLocales.nombre} onChange={...} />

      <button onClick={cerrar}>Guardar y cerrar</button>

    </div>

  );

}


📌 Resumen final:

Para evitar perder datos al desmontar:

  1. Identifica qué datos deben persistir

  2. Sube el estado al componente padre más alto necesario

  3. Usa localStorage para datos que deben sobrevivir a recargas

  4. Considera Context API para estado global complejo

  5. Guarda manualmente si el componente controla su propio ciclo de vida

En nuestro caso específico:

  • tasks ✅ está en App (correcto)

  • taskText ❌ está en TaskForm (correcto, es temporal)

  • Si queremos persistencia: agregar localStorage

Comentarios

Entradas más populares de este blog

9.3-Tutorial de Componentes React con Props -SSS

9.6-Ciclo de Vida de un Componente React

9-Componentes en React-estado