Aprende leyendo en orden

localStorage — guardar datos en el navegador

setItem, getItem y removeItem de localStorage para guardar valores en el navegador, arrays guardados como cadenas JSON, el null de las claves que faltan y el límite de espacio.

Si guardas en variables los productos del carrito o la configuración de visualización que eligió el usuario, se pierden al recargar o cerrar la página, y la próxima vez que se abra todo vuelve a su estado inicial. Hasta ahora no has visto ninguna forma de conservar valores después de volver a abrir la página.

En este artículo verás localStorage, que guarda valores en el navegador, y las cadenas JSON que se usan para guardar arrays y objetos.

Conservar valores al cerrar la página — setItem y getItem

Un sitio de noticias quiere recordar el tamaño de letra que eligió el usuario para usarlo la próxima vez que lo visite. Aunque lo guardes en una variable, como en const fontSize = "large";, las variables se pierden al recargar o cerrar la página, así que la siguiente carga no puede leerlo.

localStorage (un almacén de pares clave-valor que ofrece el navegador) guarda un valor con setItem(clave, valor), lo lee con getItem(clave) y lo elimina con removeItem(clave).

El almacenamiento está separado por origen (la parte de la URL antes de la ruta: https://news.example en https://news.example/settings), y las páginas del mismo origen leen y escriben los mismos valores.

// Si todavía hay un valor guardado de antes, lo quita para empezar sin nada guardado
localStorage.removeItem("fontSize");

// Guarda el valor cuando el usuario cambia el tamaño de letra a grande
localStorage.setItem("fontSize", "large");

// Al abrir la página, lee el valor guardado y lo usa
const fontSize = localStorage.getItem("fontSize");
console.log(fontSize);                            // large

// Guardar con la misma clave reemplaza el valor anterior
localStorage.setItem("fontSize", "small");
console.log(localStorage.getItem("fontSize"));    // small
Un almacenamiento separado para cada origen
Origen https://news.example
  • Las páginas y pestañas de este origen comparten un localStorage
localStorage: pares clave-valor
  • "fontSize""small" (reemplazado al guardar con la misma clave)
  • Se conserva al cerrar o recargar la página
La página abierta
  • Variables como const fontSize
  • Al recargar, las variables se vuelven a crear
Origen https://shop.example
  • Tiene su propio localStorage
  • No puede leer el "fontSize" que guardó news.example
Las variables se vuelven a crear al recargar, pero los valores de localStorage se conservan. Las páginas de otro origen no pueden leerlos, aunque usen la misma clave.

Si lees con getItem al abrir la página y escribes con setItem cuando el usuario cambia la configuración, la página muestra el mismo tamaño de letra incluso después de recargarla. Los valores guardados se conservan hasta que el código de la página los elimina o el usuario los borra desde la configuración del navegador.

Lo que guardas en los ejercicios se conserva para la siguiente ejecución

La consola de los ejercicios ejecuta todos los ejercicios de JavaScript del sitio en el mismo origen, así que todos comparten un único localStorage, y su contenido se conserva aunque vuelvas a abrir la página. Para que los valores guardados en ejecuciones anteriores no se mezclen con tus resultados, los ejercicios empiezan quitando con removeItem las claves que usan.

En una app de mapas, guarda el tipo de mapa y la unidad de distancia en claves separadas. La unidad de la última vez ya está guardada, y el tipo elegido, selectedMapType, ya está declarado.

① Guarda el tipo de mapa elegido.

② Lee la unidad de distancia guardada y asígnala a una variable.

③ Vuelve a guardar solo la unidad de distancia, ahora con "mile".

④ Muestra la variable de ② y los valores de las dos claves con la forma «km / mile / satellite».

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Guardar arrays y objetos — JSON.stringify

Un sitio de reservas de viaje guarda en un array los hoteles que el usuario agregó a favoritos y quiere volver a listarlos en su próxima visita. Pasar el array directamente a setItem no produce ningún error, pero en el valor que lees después no están ni los números de los hoteles ni sus nombres.

setItem convierte en cadena el valor que le pasas antes de guardarlo. Convierte los arrays y objetos en cadenas JSON con JSON.stringify antes de guardarlos, y convierte la cadena que lees de vuelta en el array u objeto original con JSON.parse.

// Si todavía hay un valor guardado de antes, lo quita primero
localStorage.removeItem("favoriteHotels");
const favoriteHotels = [{ id: "H-101", name: "Hotel Puerto" }, { id: "H-204", name: "Hostal Estación" }];

// Si lo pasas directamente, el array se convierte en cadena al guardarlo
localStorage.setItem("favoriteHotels", favoriteHotels);
console.log(localStorage.getItem("favoriteHotels"));   // [object Object],[object Object]

// Conviértelo en una cadena JSON antes de guardarlo
localStorage.setItem("favoriteHotels", JSON.stringify(favoriteHotels));
const saved = localStorage.getItem("favoriteHotels");
console.log(saved);                                    // [{"id":"H-101","name":"Hotel Puerto"},{"id":"H-204","name":"Hostal Estación"}]

// Convierte la cadena que leíste en un array antes de usarla
const restored = JSON.parse(saved);
console.log(restored[1].name);                         // Hostal Estación
El contenido se pierde en el momento de guardar
Pasa el array talcual a setItemSe guarda[object Object],…getItem devuelvela misma cadenaSe pierden losnombres de hotelJSON.stringifyy luego setItemSe guarda[{"id":"H-101",…getItem yluego JSON.parserestored[1].namees Hostal Estación
Un array que pasas directamente se convierte en la cadena [object Object] al guardarse. Después de getItem, ya no hay forma de recuperar el array original.

Los números y los booleanos también se convierten en cadena sin ningún aviso ni error, así que solo te das cuenta cuando falla un cálculo o una comprobación con if sobre el valor que leíste. La tabla de abajo muestra, para cada valor que se pasa a setItem, la cadena que se guarda y qué pasa al leerla.

Valor pasado a setItemCadena guardadaAl leerla
El número 20"20""20" + 1 da "201" (usa Number)
El booleano false"false"Es true en un if (usa JSON.parse)
Array de cadenas ["A-1", "B-2"]"A-1,B-2"Una cadena unida por comas

En una app de idiomas, agrega una palabra a la lista de palabras para repasar. La lista ya está guardada con la clave reviewWords, y la palabra que vas a agregar, newWord, ya está declarada.

① Lee la lista guardada y muestra el tipo del valor.

② Convierte el valor que leíste en un array y agrega newWord al final.

③ Vuelve a guardar el array actualizado con la misma clave.

④ Léelo otra vez, conviértelo de nuevo en array y muestra la cantidad de palabras y el word de la última.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Leer una clave que no está guardada — el null de getItem

En un sitio de recetas, los números de las recetas vistas se guardan como historial, y al abrir la página se muestra cuántas hay. Quien entra por primera vez o borró su historial no tiene nada guardado, así que, si lees el length del resultado de JSON.parse dando por hecho que hay historial, esa línea lanza un TypeError.

Si le pasas a getItem una clave que no está guardada, devuelve null, no undefined. Si escribes JSON.parse(localStorage.getItem(clave)) ?? [], se usa un array vacío solo cuando no hay nada guardado.

// Lee el historial de recetas vistas. Si no hay nada guardado, usa un array vacío
function loadHistory() {
  return JSON.parse(localStorage.getItem("viewHistory")) ?? [];
}

// Primera visita: no hay nada guardado, así que getItem devuelve null
localStorage.removeItem("viewHistory");
console.log(localStorage.getItem("viewHistory"));   // null
console.log(loadHistory().length);                  // 0

// Con un historial guardado: la cadena vuelve a ser un array
localStorage.setItem("viewHistory", JSON.stringify(["R-12", "R-30"]));
console.log(loadHistory().length);                  // 2

// Después de removeItem: vuelve a ser null
localStorage.removeItem("viewHistory");
console.log(loadHistory().length);                  // 0
Sin nada guardado, parse también devuelve null
Nada guardado(nuevo o borrado)getItemdevuelve nullJSON.parse(null)también da null?? da [], así quelength es 0Guardado["R-12","R-30"]getItem devuelveuna cadena JSONJSON.parse da unarray de 2?? no se usa;length es 2
JSON.parse no trata como error el null que devuelve getItem: simplemente devuelve null. Si agregas ?? [], puedes tratarlo como array.

Si pones el código de lectura en una función, quien la llama puede tratar el resultado como array sin comprobar si es null, incluso después de que se llame a removeItem al cerrar sesión. removeItem no lanza ningún error aunque la clave no esté guardada, así que no hace falta comprobar si hay algo guardado antes de quitarlo.

En una app de informes diarios, conserva los informes a medio escribir como borradores por fecha. Solo está guardado el borrador del día 10, y la función loadDraft, todavía vacía, ya está declarada.

① Haz que loadDraft devuelva el borrador de una fecha, o una cadena vacía si no existe.

② Lee con getItem la clave del día 11 y muestra el valor que devuelve.

③ Usa loadDraft para mostrar la longitud de los borradores del día 10 y del día 11.

④ Quita el borrador del día 10 y muestra su longitud usando loadDraft.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Recuperar el carrito después de recargar — funciones para guardar y cargar

Ahora, el carrito de una tienda en línea debe conservar su contenido cuando se recarga la página después de agregar un producto. Agregar un producto con cart.push solo cambia la variable, así que, si en algún lugar olvidas la línea que guarda, ese producto desaparece al recargar.

Pon el guardado en una función saveCart y la carga en una función loadCart, y llámalas justo después de cambiar el carrito y al abrir la página. setItem es síncrono (termina de guardar antes de pasar a la línea siguiente), así que, a diferencia de fetch, puedes leer el valor guardado en la línea siguiente sin await.

localStorage.removeItem("cart");   // Si todavía hay un carrito guardado de antes, lo quita
// Guarda el carrito como cadena JSON. No hace falta await
function saveCart(cart) {
  localStorage.setItem("cart", JSON.stringify(cart));
}
// Convierte el carrito guardado en array. Si no hay nada guardado, devuelve un carrito vacío
function loadCart() {
  return JSON.parse(localStorage.getItem("cart")) ?? [];
}

// Carga al abrir la página y guarda después de agregar un producto
const cart = loadCart();
cart.push({ id: "K-310", qty: 1 });
saveCart(cart);

// En lugar de recargar, vuelve a cargar desde la cadena guardada sin usar la variable cart
const reloaded = loadCart();
console.log(reloaded.length, reloaded[0].id);     // 1 K-310
Si olvidas saveCart, no se recupera nada
Agregar K-310con pushLlamar asaveCart(cart)Recargar yluego loadCart()reloaded.lengthes 1Agregar K-310con pushOlvidar llamara saveCartRecargar yluego loadCart()reloaded.lengthes 0
Sin una llamada a saveCart, el K-310 agregado con push solo existe en la variable cart. Después de recargar, lo que se recupera es lo último que se guardó.

Si pones todo lo que cambia el carrito en una sola función y llamas a saveCart al final, no tienes que escribir el guardado en cada lugar donde la llamas, y no puedes olvidarlo. La carga también está dentro de loadCart, así que tampoco hay ningún lugar donde puedas olvidar ?? [].

setItem lanza una excepción cuando el almacenamiento está lleno

En Chrome, un origen puede guardar unos 5.24 millones de caracteres, sumando claves y valores. Si se supera ese límite, setItem lanza un QuotaExceededError y el valor no se guarda. Las lecturas y escrituras son síncronas, así que la página también deja de responder mientras se guarda un valor grande. Usa localStorage para valores pequeños, como carritos y configuraciones.

En la pantalla de inspección de una línea de producción, guarda los defectos encontrados con un conteo para cada número de pieza. saveDefects, loadDefects y la función addDefect, todavía vacía, ya están declaradas.

① En addDefect, lee los registros guardados y busca uno con el mismo número de pieza.

② Si lo encuentras, suma 1 a su conteo; si no, agrega un registro con conteo 1. Después, guarda.

③ Registra defectos de W-12, W-40 y W-12, en ese orden.

④ Vuelve a leer los registros con loadDefects y muestra cada número de pieza con la forma «W-40: 1».

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Qué pasa si ejecutas localStorage.getItem("theme") con una clave que nunca se guardó?

Pregunta 2Después de localStorage.setItem("darkMode", false), ¿qué pasa si usas el valor leído como condición de un if?

Pregunta 3Si recargas después de cart.push(book) sin llamar a saveCart, ¿qué devuelve loadCart()?