Dario Paladines
Raycris Maldonado
Yo tambien vi esa solucion!
Luis Alejandro Vera Hernandez
Super!
Mostrar la lista de películas favoritas en una app con JavaScript implica leer el objeto guardado en localStorage, convertirlo en array y renderizarlo en el DOM. Esta guía explica cómo lograrlo, sincronizar el estado visual de los botones de like y resolver los errores de navegación que aparecen cuando agregas una nueva sección al HTML.
Cómo leo las películas favoritas desde localStorage
La idea es replicar la lógica de funciones como getTrendingMovies o getMoviesBySearch, pero en vez de consumir una API, consumimos localStorage.
Para empezar, creas una función getLikedMovies que invoca a la función likedMoviesList, la cual hace JSON.parse de lo que viene guardado y devuelve un objeto con cada película indexada por su id.
¿Cómo convierto un objeto de localStorage en un array? Usa
Object.values(miObjeto). Ese método devuelve un array con todos los valores del objeto, ignorando las llaves, listo para iterarlo o pasarlo a una función de renderizado.
Una vez tienes el array, lo pasas a createMovies junto con el contenedor, el flag de lazy loading y el flag para limpiar el HTML previo:
Object.values(likedMovies).likedMoviesListArticle seleccionado con document.querySelector.true por si la lista crece.true para evitar duplicados al recargar.Un detalle fácil de pasar por alto: al definir el selector en el archivo de nodos, el querySelector necesita el punto inicial para clases (.liked-movies-list) o el # para IDs. Sin ese punto, recibes el clásico error Cannot read properties of null (reading 'innerHTML').
Por qué los botones de like se ven desincronizados al recargar
Cuando recargas la página, los botones de corazón pierden la clase movie-button-liked porque el DOM se reconstruye desde cero. Aunque la información persiste en localStorage, la UI no la refleja hasta que la consultas explícitamente al pintar cada tarjeta.
Cómo sincronizo el estado visual del botón con localStorage
Dentro de la función createMovies, justo donde defines el botón de like, agregas un condicional que pregunta si el id de esa película existe en el objeto que devuelve likedMovieList():
javascript if (likedMovieList()[movie.id]) { movieBtn.classList.add('movie-button-liked'); }
Aquí no usas toggle, porque toggle invierte el estado. Lo que necesitas es solo añadir la clase cuando la película ya esté guardada. El toggle se queda únicamente para el event listener del clic, donde sí queremos alternar.
¿Qué hace Object.values en JavaScript? Recibe un objeto y devuelve un array con todos sus valores, en el orden en que fueron declarados. Es la forma más limpia de iterar valores de un objeto sin tocar las claves.
Cómo arreglo la navegación entre secciones
Al agregar la sección de favoritos al HTML, esta aparece en todas las rutas porque nunca le aplicas la clase inactive. La solución vive en navigation.js.
Primero, declaras el nodo en el archivo de selectores:
likedMoviesSection apunta al elemento con id liked.navigation.js junto con el resto.add y remove que ya usas para trendingPreviewSection.La regla práctica: cada vez que trendingPreviewSection recibe classList.remove('inactive') (en homePage), también se lo quitas a likedMoviesSection. Y cada vez que se lo agregas en categoriesPage, movieDetailsPage, searchPage o trendsPage, repites el add('inactive') para favoritos.
Con eso, la sección de películas favoritas solo aparece en el home y desaparece en categorías, búsqueda, tendencias y detalle de película.
Cómo escucho cambios de localStorage en tiempo real
Queda un detalle: si das like desde la página de tendencias sin navegar a otra ruta, la lista de favoritos del home no se actualiza hasta que recargas o cambias de sección. La razón es que getLikedMovies solo se ejecuta cuando se llama a homePage.
La solución es escuchar el evento storage del objeto window. Funciona como cualquier otro event listener:
javascript window.addEventListener('storage', getLikedMovies);
Cada vez que algo cambie en localStorage, el evento se dispara y vuelves a renderizar la lista. La documentación de MDN sobre Storage Event es la referencia recomendada para profundizar.
¿Cuándo se dispara el evento storage en JavaScript? Se dispara cuando otro documento del mismo origen modifica localStorage o sessionStorage. En la misma pestaña que hace el cambio puede no dispararse, así que a veces conviene emitir un evento personalizado tras escribir.
Qué aprendiste sobre persistencia y DOM en esta práctica
Resolver este flujo te entrena en habilidades concretas:
querySelector y respeto a la sintaxis de selectores CSS.localStorage usando JSON.parse y JSON.stringify.Object.values para reutilizar funciones de renderizado.inactive y event listeners de navegación.El reto abierto es claro: implementa el event listener de storage para que la lista de favoritos se actualice sin recargar ni navegar. Cuéntame en los comentarios qué nombre le habrías puesto al nodo likedMoviesListArticle y cómo resolviste la actualización en tiempo real.
Dario Paladines
Raycris Maldonado
Yo tambien vi esa solucion!
Luis Alejandro Vera Hernandez
Super!
Actualización de favoritos . Intente hacerla con el Windows.storage.event pero no pude. Leí la documentación y al parecer solo funciona cuando el localStorage se modifica desde otro documento. Hasta hay una nota al comienzo que dice: .
Note: This won’t work on the same page that is making the changes — it is really a way for other pages on the domain using the storage to sync any changes that are made. Pages on other domains can’t access the same storage objects.
. Quisiera saber si estoy en lo correcto y entendí bien, o de lo contrario como podría realizarla.
Yo lo solucioné de una forma bastante sencilla, haciendo recargar el home cuando se realiza un cambio estando en el mismo. Así como está configurado el cache, el cambio es instantáneo con cualquier velocidad de internet. Antes intenté hacerla llamando a la funcion getLikedMovies() en vez de al homePage(), pero al quitar una película de favoritos sigue manteniendo el estilo en el resto de las secciones.
function likeMovie(movie) { ... if (location.hash == ''){ homePage(); } }
Tuve que complementar tu condicional así:
if (location.hash.startsWith('#home') || !location.hash) { homePage(); }
Para que en caso que esté en #home luego de ir a otra sección y regresar todavía funcione.
Enrique Aguilera
Ya que no me funcionó el window.storage, lo que hice fue volver a llamar a las funciones getTrendingMoviesPreview() y getLikedMovies() al final de la funcion likeMovies() y de momento funciona bien
function likeMovies(movie) { const likedMovies = likesMoviesList() if(likedMovies[movie.id]) { likedMovies[movie.id] = undefined } else { likedMovies[movie.id] = movie } localStorage.setItem("liked-movies", JSON.stringify(likedMovies)) getTrendingMoviesPreview() getLikedMovies() }
Víctor Fabián Morales Rodríguez
jeje yo hice exactamente lo mismo :)
Lautaro Strappazzon
Juan Castro
Profesor:100:
Daniel González
Si quieren ocultar toda la sección de favoritos cuando no hayan películas seleccionadas, se puede agregar la clase inactive dentro de la función getLikedMovies() de la siguiente forma:
function getLikedMovies() { const likedMovies = likedMovieList(); const moviesArray = Object.values(likedMovies); likedMoviesListArticle.innerHTML = ''; !moviesArray.length && likedMoviesSection.classList.add('inactive'); createMovies(moviesArray, likedMoviesListArticle); }
Leonard Sumoza
Saludos, por mi parte solo inserte la función homepage() en el evento de hacer click en el botón de me gusta de, así cada que se cliquee para gustar o no una película la pagina inicial se recargara
<code> movieBtn.addEventListener('click', () => { movieBtn.classList.toggle('movie-btn--liked'); likeMovie(movie); homePage(); });
este código se encuentra dentro de la función createMovies()
fabio andres zamora osorio
Como se llama esta forma de hacer condicionales que implemento el profesor para buscar informacion y estudiarla:
likedMoviesList()[readMovies.id] && movieBtn.classList.add('movie-btn--liked')
la verdad no la conocia yo solo conocia los condicionales con if/else o con el operador ternario
Juan Castro
ProfesorSe llama Logical AND: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND
Fernando Yutiz
Mi solución fue tan sencilla, que aun sigo mirando si cometí algun error jaja Solo agregue un llamado a homepage() desde al final del evento click del botón movieBtn.
Ricardo Alfonso Chavez Vilcapoma
Llegaste aquí
ERES UN CRACK
Siguiente paso:
REACT O ALGÚN FRAMEWORK
Juan Cortez
Mi solución fue llamar a homePage para que refresque la página. No sé si está bien, pero fue una solución rápida. En este caso, item reemplaza a movie.
movieBtn.addEventListener('click',()=>{ movieBtn.classList.toggle('movie-btn--liked'); likeMovie(item); homePage(); })
Alonso Chaves
Yo intenté hacer algo parecido pero con window.addEventListener('click', navigator, false) en navigation.js
Pero tiene el inconveniente de que si se busca una pelicula muy abajo, al darle click al favorito se recarga la pagina y hay que volver a buscar por donde iba uno. Funciona, pero es mejor solución de el estudiante Dario Paladines (El aporte con mas likes 40+) para que no haya necesidad de recargar y que el usuario se pierda. En mi opinión.
Johnner Alexander Gambacica Ortiz
Un push a la lista de películas favoritas
Leonardo Jaimovich
yo llame en la función q actualiza el storage a las funciones q refrescan tanto las trending movies preview como las peliculas likeadas. Anda perfecto y actualiza todos los botones de like, aparecen y desaparecen de la lista de likeadas,etc. Voy a probar con el evento del localStorage, supongo q es mucho mas elegante!
Sergio Javier Lopez Olivera
Esta fue mi solución, ejecutar la función likedMovies y recorrer el arreglo resultante verificando si la película en cuestión tenía el mismo ID de la película que se estaba renderizando en ese momento en la función renderMovie (createMovie): .
const moviesArr = Object.values(likedMoviesList()); if(moviesArr){ moviesArr.forEach(m => { if(m.id == movie.id){ movieBtn.classList.add('movie-btn--liked'); } else { movieBtn.classList.add('movie-btn'); } movieBtn.addEventListener('click', () => { movieBtn.classList.toggle('movie-btn--liked'); likeMovie(movie) //function to add movies to localStorage and to the favorites section }) }) } movieContainer.append(movieImage); movieContainer.append(movieBtn);
Luis Paredes
la solucion a la que llegue es sencilla pero me tomo como una hora llegar a ella para enviar los datos de forma correcta
function getFavoriteMovies () { const movies = likedMovieList() const moviesArray = Object.values(movies) console.log(Object.values(movies)) if (movies != {}) { createMovieList(moviesArray, favoriteMovieList, { lazyLoad : true , clean : true } ) } }
Marcelo Jorge Bellizia
Yo intente hacer esto antes de verlo y lo único que hice distinto es la parte de darle el estilo o no. Por si a alguien le sirve mi lógica:
const existMovieInLikes= localStorage.liked_movies.includes(movie.id); console.log(existMovieInLikes) if (existMovieInLikes){ movieBtn.classList.add("movie-btn","movie-btn--liked"); }else{ movieBtn.classList.add("movie-btn") } movieBtn.addEventListener("click", ()=>{ movieBtn.classList.toggle("movie-btn--liked") likeMovie(movie); insertFavoritedMovies(); /* Y aca hice que se ejecute la accion cada vez que se haga click en el boton para actualizar la seccion de likes*/ })
Marcelo Jorge Bellizia
`que se ejecute la funcion*
CRISTIAN DARIO AGUDELO PORRAS
Mi solucion fue crear una funcion que verifica si movie-id esta en la lista de localStorage, y si ese es el caso, entonces agregar la clase movie-btn--liked
aqui dejo mi solucion
function isLiked(id) { const movieList = likedMovieList(); return movieList[id] ? true : false } if(isLiked(movie.id)){ movieBtn.classList.add('movie-btn--liked'); }
Jose Ever Muñoz Muñoz
Solo hay ejecutar getLikedMovies() en el evento click de del movie btn
$movieBtn.addEventListener('click', () => { $movieBtn.classList.toggle('movie-btn--liked'); likeMovie(movie); //esta es la línea agregada, sea actualizara cada vez que demos click al boton de like getLikedMovies(); })
Daniel Rodriguez
Mi solucion
likeBtn.addEventListener("click", () => { likeBtn.classList.toggle("like-btn--liked"); likeMovie(movie); if(location.hash = "#home"){ getLikedMovies(); } });
Axel Enrique Galeed Gutierrez
Manuel Araujo
Para actualizar la sección de favoritos siempre que se haga clic sobre el botón de corazón, modifiqué la función likeMovie(). Lo primero que creó el profesor en esa función fue una variable likedMovies que contiene a la ejecución de la función likedMoviesList(), entonces, justo después de que se reescribe la información en local storage, creé una nueva variable llamada NewLikedMovies que contiene también a la ejecución de la función likedMoviesList() pero esta vez obviamente luego de la sobreescritura. Luego establezco un condicional para que si hay diferencia entre la primera variable y la segunda, ejecute la función getLikedMovies();
Así queda la función:
function likeMovie(movie) { const likedMovies = likedMoviesList(); console.log(likedMovies); if(likedMovies[movie.id]) { likedMovies[movie.id] = undefined; console.log('La pelicula ya esta en storage'); } else { likedMovies[movie.id] = movie; console.log('La pelicula no esta en storage'); } localStorage.setItem('liked_movies', JSON.stringify(likedMovies)); const NewlikedMovies = likedMoviesList(); if(likedMovies !== NewlikedMovies) { getLikedMovies(); } }
Creo que la solución más fácil que pude encontrar es simplemente ejecutar la función getLikedMovies() en el listener, literalmente solo agregue esa linea y por el momento funciona en todas las paginas que se agrega la película sin necesidad de recargar
movieBtn.addEventListener('click', ()=>{ movieBtn.classList.toggle('btnMovie--liked') likeMovie(movie); getLikedMovies() //única linea agregada })
Basándome en el excelente aporte previo de Jonathan Alfonso, me dí cuenta que si hacía scroll horizontal en TrendingPreview y le daba like a una película, al recargárseme completamente el home el scroll horizontal volvía al comienzo. Siento que ++esto puede afectar un poco la Experiencia del Usuario++, por lo que me pareció mejor que la función likeMovie llame a getTrendingPreviewMovie y a getLikedMovies:
function likeMovie(movie) { ... if (location.hash == ''){ getLikedMovies(); getTrendingMoviesPreview(); } }
Estas dos funciones limpian sus contenedores de scroll para que no se dupliquen las películas:
const getLikedMovies = () => { likedMoviesScroll.innerHTML = ""; let likedMovies = Object.values(likedMovieList()); printMoviePosters(likedMovies, likedMoviesScroll, true); } const getTrendingMoviesPreview = async () => { const { data } = await api('/trending/movie/day'); const movies = data.results; trendingMoviesPreviewList.innerHTML = ""; printMoviePosters(movies, trendingMoviesPreviewList, true); }
Guardar favoritos en la API de TMDB
Les comparto la forma de agregar y eliminar las películas de favoritos usando la API de TMDB V3. Les dejo el código utilizando Axios como fetch. 😀
Recuerden leer siempre la documentación de la API atentamente, así no tienen dólares de cabeza como yo los tuve. 😅
fetch
Obtener request_token
const getRequestToken = async () => { const REQUEST_TOKEN_URL = 'authentication/token/new'; const init = { method : 'GET', headers : { 'Content-Type' : 'application/json;charset=utf-8' } }; const RESPONSE = await fetch(`${URL}/${REQUEST_TOKEN_URL}?api_key=${YOUR_API_KEY}`, init); const DATA = await RESPONSE.json(); const REQUEST_TOKEN = DATA.request_token; return REQUEST_TOKEN; };
Obtener session_id
Antes de obtener nuestro session_id tenemos que aceptar la autenticación de terceros desde nuestra cuenta de TMDB, lo realizamos desde la siguiente URL
www.themoviedb.org/authenticate/{your_request_token}.
Una vez aceptado ya podés seguir con el siguiente código.
const getSession = async () => { const SESSION_ID_URL = 'authentication/session/new'; const request_token = 'your_request_token'; const REQUEST_BODY = JSON.stringify({ request_token }); const init = { method : 'POST', headers : { 'Content-Type' : 'application/json;charset=utf-8' }, body : REQUEST_BODY }; // *Tenemos que aceptar, en nuestra cuenta de The Movie DB, la autenticación de terceros en https://www.themoviedb.org/authenticate/{request_token} const RESPONSE = await fetch(`${URL}/${SESSION_ID_URL}?api_key=${YOUR_API_KEY}`, init); const DATA = await RESPONSE.json(); const SESSION_ID = DATA.session_id; return SESSION_ID; };
Obtener account_id
const getAccountId = async () => { const SESSION_ID = 'your_session_id'; const ACCOUNT_URL = 'account'; const init = { method : 'GET', headers : { 'Content-Type' : 'application/json;charset=utf-8' }, }; const RESPONSE = await fetch(`${URL}/${ACCOUNT_URL}?api_key=${YOUR_API_KEY}&session_id=${SESSION_ID}`, init); const ACCOUNT = await RESPONSE.json(); const ID = ACCOUNT.id; return ID; };
Agregar o eliminar una película de favoritos
El parámetro favorite(boolean) es el que nos permite agregar o eliminar una película de favoritos.
favorite = true add movie.
favorite = false remove movie.
const addOrDeleteFavoriteMovie = async ({ media_id, remove = false }) => { // Si queremos eliminar la película de favoritos tenemos que pasar el parámetro favorite = false; const SESSION_ID = 'your_session_id'; const media_type = 'movie'; const favorite = !remove; const ACCOUNT_ID = await getAccountId(); const REQUEST_BODY = JSON.stringify({ media_type, media_id, favorite }); const init = { method : 'POST', headers : { 'Content-Type' : 'application/json;charset=utf-8' }, body : REQUEST_BODY }; const RESPONSE = await fetch(`${URL}/account/${ACCOUNT_ID}/favorite?api_key=${YOUR_API_KEY}&session_id=${SESSION_ID}`, init); const STATUS = await RESPONSE.json(); return STATUS; };
Ver las películas guardadas
const getFavoriteMovies = async () => { const SESSION_ID = 'your_session_id'; const ACCOUNT_ID = await getAccountId(); const init = { method : 'GET', headers : { 'Content-Type' : 'application/json;charset=utf-8' } }; const RESPONSE = await fetch(`${URL}/account/${ACCOUNT_ID}/favorite/movies?api_key=${YOUR_API_KEY}&session_id=${SESSION_ID}`); const DATA = await RESPONSE.json(); const MOVIES = DATA.results; return MOVIES; };
Axios
Una cosa para aclarar, el parámetro params son los query parameters y nuestro parámetro data es el body que utilizamos con fetch.
Creando instancia para usar la API
const config = { baseURL : 'https://api.themoviedb.org/3/', headers : { 'Content-Type' : 'application/json;charset=utf-8' }, params : { api_key : YOUR_API_KEY } }; const api = axios.create(config);
Obtener request_token
const getRequestToken = async () => { const RESPONSE = await api('authentication/token/new'); const DATA = RESPONSE.data; const REQUEST_TOKEN = DATA.request_token; return REQUEST_TOKEN; };
Obtener session_id
Antes de obtener nuestro session_id tenemos que aceptar la autenticación de terceros desde nuestra cuenta de TMDB, lo realizamos desde la siguiente URL
www.themoviedb.org/authenticate/{your_request_token}.
Una vez aceptado ya podés seguir con el siguiente código.
const getSession = async () => { const request_token = 'your_request_token'; const config = { params : { request_token } }; // *Tenemos que aceptar, en nuestra cuenta de The Movie DB, la autenticación de terceros en https://www.themoviedb.org/authenticate/{request_token} const RESPONSE = await api('authentication/session/new', config); const DATA = RESPONSE.data; const SESSION_ID = DATA.session_id; return SESSION_ID; };
Obtener account_id
const getAccountId = async () => { const session_id = 'your_session_id'; const config = { params : { session_id } }; const RESPONSE = await api('account', config); const ACCOUNT = RESPONSE.data; const ID = ACCOUNT.id; return ID; };
Agregar o eliminar una película de favoritos
El parámetro favorite(boolean) es el que nos permite agregar o eliminar una película de favoritos.
favorite = true add movie.
favorite = false remove movie.
const addOrDeleteFavoriteMovie = async ({ media_id, remove = false }) => { // Si queremos eliminar la película de favoritos tenemos que pasar el parámetro favorite = false; const session_id = 'your_request_token'; const media_type = 'movie'; const favorite = !remove; const ACCOUNT_ID = await getAccountId(); const config = { method : 'POST', params : { session_id }, data : { media_type, media_id, favorite } }; const RESPONSE = await api(`account/${ACCOUNT_ID}/favorite`, config); const STATUS = RESPONSE.data; return STATUS; };
Ver las películas guardadas
const getFavoriteMovies = async () => { const session_id = 'your_request_token'; const ACCOUNT_ID = await getAccountId(); const config = { params : { session_id } }; const RESPONSE = await api(`account/${ACCOUNT_ID}/favorite/movies`, config); const DATA = RESPONSE.data; const MOVIES = DATA.results; return MOVIES; };
PD: No puedo dejar los links directos a la explicación de cada sección de la página de TMDB, ya que el sistema de comentarios lo detecta como “enlace malicioso”. Si quieren mayor detalles, pueden ver directo en la página de la API.