Contenido del curso

Persistencia con localStorage y useEffect

Resumen

Guardar el estado de una aplicación en localStorage permite que los cambios del usuario, como editar una lista de tareas o activar el dark mode, sigan ahí cuando vuelva a entrar. La clave está en tratar esa persistencia como un side effect y manejarla con useEffect para sincronizar la reactividad con el almacenamiento del navegador.

¿Por qué guardar en localStorage es un side effect?

Cuando escribes en localStorage, sales del sistema de renderizado y reactividad de tu aplicación. Eso es exactamente lo que define a un side effect: una operación que ocurre fuera del flujo normal de render.

Por eso usamos useEffect, una primitiva que reacciona a cambios y vuelve a ejecutar su lógica cada vez que sus dependencias internas cambian [0:30]. En una lista de todos, que internamente es un proxy con trampas reactivas en cada propiedad, cualquier modificación dispara el efecto y guarda el nuevo estado.

¿Qué es un side effect en una app reactiva? Es cualquier operación que ocurre fuera del ciclo de render, como escribir en localStorage, hacer fetch o tocar el DOM directamente. Se maneja con useEffect para que se ejecute cuando cambien las dependencias.

¿Cómo guardar la lista de todos en localStorage?

Dentro del effect, llamas a localStorage.setItem con una key (por ejemplo, todos) y el valor serializado con JSON.stringify de tu lista actual [1:10].

Cada vez que agregas un ítem, marcas uno como completed o eliminas un elemento, el efecto se vuelve a ejecutar y reescribe el valor en localStorage. Lo puedes verificar abriendo la pestaña Application del navegador y revisando la entrada todos:

  • Al agregar un elemento, aparece en el JSON guardado.
  • Al cambiar el completed de un ítem, el booleano se actualiza de false a true o viceversa.
  • Al eliminar, el ítem desaparece del localStorage.

Hasta aquí ya estás escribiendo, pero todavía no estás leyendo esos datos al iniciar la app.

¿Cómo inicializar el store con datos de localStorage?

Al crear el store de todos, lee primero lo que haya en localStorage. Lo haces con JSON.parse(localStorage.getItem('todos')) y guardas el resultado en una variable, por ejemplo todosLocalstorage [2:50].

Luego usas esa variable como valor inicial del store, con un fallback a tus datos por defecto si no existe nada guardado:

  • Si el usuario ya tiene datos, la app arranca con su estado anterior.
  • Si entra por primera vez, ve los valores por defecto para que la lista no esté vacía.

Con esto, refrescar la página deja de borrar el progreso. Agregas, completas, recargas y todo sigue ahí.

¿Cómo persistir el dark mode con localStorage?

El toggle del dark mode sigue la misma idea, pero con un detalle: el valor en localStorage siempre es un string. En el componente ButtonDarkMode, en lugar de inicializar el signal en false, lees localStorage.getItem('darkMode') y lo comparas contra el string 'true' [4:30].

Esa comparación funciona incluso si la key no existe, porque getItem devuelve null, y null === 'true' es false. Así arrancas en light mode sin lógica extra.

¿Cómo guardo un booleano en localStorage? localStorage solo guarda strings. Conviertes el booleano a texto al escribir y comparas contra 'true' al leer, o usas JSON.parse y JSON.stringify para mantenerlo tipado.

Dentro del effect que ya reaccionaba al cambio de dark mode para actualizar la clase del body, agregas otro side effect con localStorage.setItem('darkMode', valorActual). Cada toggle actualiza la clase y persiste el valor.

¿Qué pasa al refrescar después del cambio?

Si activas dark mode, recargas y la app sigue oscura. Si vuelves a light mode y recargas, sigue clara. El estado visual del usuario se respeta entre sesiones.

¿Qué reto sigue para practicar listas derivadas?

El reto extiende la to-do list con una segunda lista llamada completed, donde aparecen automáticamente los elementos marcados como completados. Desde esa lista también puedes eliminarlos, y si le quitas el estado de completado a un ítem, regresa a la lista original.

Para resolverlo necesitas:

  1. Crear dos listas mediante signals derivados o memos, una con los pendientes y otra con los completados.
  2. Renderizar cada lista con su propio ciclo for.
  3. Calcular bien el índice real del elemento al editar o cambiar su estado, ya que las listas derivadas no comparten el índice con el array original.

¿Qué es un signal derivado o memo? Es un valor reactivo calculado a partir de otros signals. Se recalcula automáticamente cuando cambia su dependencia, ideal para filtrar listas como pendientes y completados sin duplicar estado.

Si te animas, comparte tu solución en los comentarios y cuenta cómo resolviste el manejo del índice entre la lista original y las derivadas.