No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Infinite Scrolling: closures de navegaci贸n

13/20
Recursos

Aportes 23

Preguntas 5

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

Deben quitar el async de la funci贸n 鈥渋nvocadora鈥 del closure, si lo dejan, cosas raras pasan y se pueden pasar dos horas debuggeando.

Odio y adoro este tipo de clases.

Resumen de Closure
.
Un closure es la combinaci贸n entre una funci贸n y el 谩mbito l茅xico en el que esta fue declarada. Con esto, la funci贸n recuerda el 谩mbito en el que se cre贸

Recuerda la asignaci贸n anterior
.
En esta clase se explica esto y se muestra un ejemplo

Ahora hay que implementar un bot贸n para volver al inicio de la p谩gina cuando estemos haciendo el scroll infinito

En la navegaci贸n, cada vez que se a帽ade el evento del scroll con la funcion de paginaci贸n a ejecutar, se deber铆a de devolver la page = 1, para limpiar la variable de las posibles navegaciones que se realicen en otras secciones, con esto aseguramos que el endpoint que se vaya a ejecutar cargue de la p谩gina 1++.

https://www.youtube.com/watch?v=JXG_gQ0OF74
A pesar de que a煤n no los domino, este vide铆to del Profesor Sasha en su canal de La Cocina del C贸digo (canal super recomendado 馃槑鉁) me ayud贸 mucho a entender c贸mo funcionan los closures con profundidad, je je.

Yo como no tengo seccion de tendencias hacia abajo lo implemente en search primero y lo habia puesto:

infiniteScroll = ()=> getPaginatedMovies(keyword) ;

Es la primera vez que veo 煤til los closures en js.

FUN FACT: 鈥淒rama鈥 es la categor铆a con mas pel铆culas.
Tiene +9,400 pages x 20movies = 188,000movies

Yo lo resolvi de la siguiente forma:

infiniteScroll = () => { getPaginatedCategories(categoryId)};

yo implemente esta solucion y me funciono bien hasta donde he probado

infiniteScroll = () => {
    getPaginatedMoviesBySearch(realQuery)}

Falto resetear la variable page
Puede que faltara asignar a la variable global 鈥減age鈥 el valor 1, debido al pasar a otra secci贸n(vista de pel铆culas por categor铆as, vista de pel铆culas por busqueda) si el usuario ha navegado previamente en otras secciones que modifican a page como la de trending movies probablemente el valor de page tendr谩 un valor mayor a 1 y ese valor ser谩 utilizado para solicitar pel铆culas en las dem谩s secciones que lo requieran al realizar un scroll.

Hola, tengo un par de apuntes que yo considero importantes para compartir:

  • Los closures son una caracter铆stica poderosa de JavaScript que permite crear funciones que pueden recordar el estado de las variables en el momento en que se crearon, lo que permite una mayor flexibilidad y funcionalidad en el c贸digo.

  • Por otro lado, el currying es una t茅cnica de programaci贸n funcional que consiste en transformar una funci贸n con varios argumentos en una serie de funciones que toman uno o m谩s argumentos. La funci贸n curried devuelve otra funci贸n que toma el siguiente argumento, y as铆 sucesivamente, hasta que se han proporcionado todos los argumentos necesarios.

  • En ambas t茅cnicas, se utilizan funciones anidadas en JavaScript para lograr su funcionalidad. En el caso de los closures, la funci贸n interna (anidada) se utiliza para acceder a las variables de su 谩mbito externo, mientras que en el currying, la funci贸n interna (anidada) se utiliza para devolver una nueva funci贸n que toma un argumento adicional.

Les comparto mi soluci贸n para el scroll infinito para cada una de las p谩ginas. 馃槃
Repositorio de GitHub

export const getPaginatedMovies = async () => {
	const {
		scrollTop: SCROLL_TOP,
		scrollHeight: SCROLL_HEIGHT,
		clientHeight: CLIENT_HEIGHT
	} = document.documentElement;
	const IS_SCROLL_BOTTOM = (SCROLL_TOP + CLIENT_HEIGHT) >= (SCROLL_HEIGHT - 15);
	const IS_NOT_HOME = location.hash !== '#home';

	if (IS_SCROLL_BOTTOM && IS_NOT_HOME) {
		const HASHES_ROUTES = {
			'#trends'   : 'trending/movie/day',
			'#category' : 'discover/movie',
			'#search'   : 'search/movie'
		};
		const DATA_OF_HASH = location.hash.split('=');
		const HASH_NAME = DATA_OF_HASH[0];
		const EXTRA_INFO = DATA_OF_HASH[1];
		const ROUT = HASHES_ROUTES[HASH_NAME];
		const params = {
			page        : pageMovies(),
			with_genres : '',
			query       : ''
		};

		if (HASH_NAME === '#category') {
			const [ID_CATEGORY] = EXTRA_INFO.split('-');

			params.with_genres = ID_CATEGORY;
		}

		if (HASH_NAME === '#search') {
			const QUERY_SEARCH = EXTRA_INFO;

			params.query = QUERY_SEARCH;
		}

		const RESPONSE = await api(ROUT, { params });
		const DATA = RESPONSE.data;
		const MOVIES = DATA.results;
		const IS_MAX_PAGE = DATA.page > DATA.total_pages;

		if (IS_MAX_PAGE)
			return;
		const IS_CAROUSEL = false;

		insertMovies(MOVIES, GENERIC_LIST_CONTAINER, IS_CAROUSEL, { clean: false });
	}
};

export const currentPageMoviesUpdate = () => {
	let pageMovies = 1;

	return function (refresh = false) {
		pageMovies = (refresh)
			? 1
			: pageMovies + 1;

		return pageMovies;
	};
};

Para los que no son fan de los closures les comparto mi soluci贸n al problema 鈥渃omo obtener el query鈥:

Espero les sea 煤til!

Este hab铆a sido como lo resolv铆

// Navigation.js
getPaginatedMoviesBySearch.query = query
getPaginatedMoviesBySearch = getPaginatedMoviesBySearch.bind(getPaginatedMoviesBySearch)
infiniteScroll = getPaginatedMoviesBySearch
// Function getPaginatedMoviesBySearch
query = this.query

Yo cuando hizo return de una funci贸n: *Qued茅

Hola, si tienen el error de que no les funciona la funcion de, porque como copiamos la misma funcion de getMoviesBySearch y esta viene con un async, debemos de quitarselo, aqui un ejemplo mas claro

ANTES NO FUNCIONANDO

const getPaginatedMoviesBySearch = async (query) => {
  return async function () {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
    const scrollIsBottom = scrollTop + clientHeight >= scrollHeight - 15;
    const pageIsNotMax = page < maxPage;

    if (scrollIsBottom && pageIsNotMax) {
      page++;
      const { data } = await api(`search/movie`, {
        params: {
          query,
          page,
        },
      });

      const movies = data.results;
      createMovies(movies, genericSection, { lazyLoad: true, clean: false });
    }
  };
};

FUNCIONANDO
La unica diferencia es la palabra reservada async que causa que no funcione.

const getPaginatedMoviesBySearch = (query) => {
  return async function () {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
    const scrollIsBottom = scrollTop + clientHeight >= scrollHeight - 15;
    const pageIsNotMax = page < maxPage;

    if (scrollIsBottom && pageIsNotMax) {
      page++;
      const { data } = await api(`search/movie`, {
        params: {
          query,
          page,
        },
      });

      const movies = data.results;
      createMovies(movies, genericSection, { lazyLoad: true, clean: false });
    }
  };
};

Agregue el Infinite Scrolling a las pel铆culas relacionadas

function getPaginatedRelatedMovies(id) {
    return async function() {
        const scrollWidthScreen = document.documentElement.scrollWidth;
        const { scrollLeft, scrollWidth } = relatedMoviesContainer;
        const scrollIsEnd = ((scrollWidth + scrollLeft) >= (scrollWidthScreen - 100));
        const pageIsNotMax = page < maxPage;
        if (scrollIsEnd && pageIsNotMax) {
            page++;
            const { data } = await api(`movie/${id}/similar`, {
                params: {
                    page
                }
            });
            const movie = data.results;
            printMovies(movie, relatedMoviesContainer, {clean: false, lazyLoad: true})
        }
    }
}

Agregu茅 el evento listener al contenedor de pel铆culas relacionadas

relatedMoviesContainer.addEventListener("scroll", infiniteScroll, false);

El contenedor me empez贸 a dar problemas as铆 que le agrege el max-height

.relatedMovies-scrollContainer {
  position: absolute;
  overflow-x: scroll;
  overflow-y: hidden;
  white-space: nowrap;
  width: calc(100vw - 24px);
  padding-bottom: 16px;
  max-height: 187px;
}

Esta solucion me gusta bastante, yo en mi caso hice que infinite scroll sea una funcion anonima, en donde al ejecutarla por el evento scroll llame a la funcion paginada:

function searchPage(){
    console.log('Search !!');

    headerSection.classList.remove('header-container--long');
    headerSection.style.background = '';
    arrowBtn.classList.remove('inactive');
    arrowBtn.classList.remove('header-arrow--white');
    headerTitle.classList.add('inactive');
    headerCategoryTitle.classList.add('inactive');

    searchForm.classList.remove('inactive');

    trendingPreviewSection.classList.add('inactive');
    categoriesPreviewSection.classList.add('inactive');
    genericSection.classList.remove('inactive');
    movieDetailSection.classList.add('inactive');

    const [ _ , query] = location.hash.split('=');

    getMoviesBySearch(query);

    infiniteScroll = () => {
        getPaginetedMoviesBySearch(query)
    };

}

Aqui en este caso tambien se aplicaria closure, ya que la funcion interna siempre recuerda esa variable de la funcion padre (query). Asi sea que la funcion padre (searchPage) haya terminado.

Excelente clase! Que gran uso de closures!

Continuando con mi aporte de la clase pasada, y mejor谩ndolo gracias a lo aprendido en esta clase, ya no es necesario volver a conseguir el query ni el categoryId del hash como lo hac铆a:

main.js

function getPaginatedMoviesByCategory() {
    const [_, categoryData] = location.hash.split('=');
    const [categoryId] = categoryData.split('-');
    getPaginatedMovies('/discover/movie', {categoryId});
}

function getPaginatedMoviesBySearch() {
    const [_, undecodedQuery] = location.hash.split('=');
    const query = decodeURI(undecodedQuery);
    getPaginatedMovies('/search/movie', {undefined, query});
}

Sino que directamente se los paso como par谩metro en navigation.js:

    infiniteScroll = getPaginatedMoviesByCategory(categoryId);

    infiniteScroll = getPaginatedMoviesBySearch(query);

Y retorno una funci贸n que ejecuta la funci贸n global de paginaci贸n:

main.js

function getPaginatedMoviesByCategory(categoryId) {
    return function () {
        getPaginatedMovies('/discover/movie', {categoryId});
    }
}

function getPaginatedMoviesBySearch(query) {
    return function () {
        getPaginatedMovies('/search/movie', {undefined, query});
    }
}

async function getPaginatedMovies(
    endPoint, 
    {
        categoryId, 
        query
    } = {},
    ) {
    const { 
        scrollTop, 
        scrollHeight, 
        clientHeight 
    } = document.documentElement;

    const scrollAtBottom = (scrollTop + clientHeight) >= (scrollHeight - 15);
    const pageIsNotMax = page < maxPage;

    if (scrollAtBottom && pageIsNotMax) {
        page++;

        const { data } = await api(endPoint, {
            params: {
                page,
                with_genres: categoryId,
                query,
            },
        });
        const movies = data.results;

        printMoviePosters(movies, genericSection, true);
    } else if (scrollAtBottom && !pageIsNotMax) {
        maxPageReached.classList.remove('inactive');
    };
}

Ten铆a entendido esto de CLOSURE

Me estaba confundiendo un poco por la sintaxis del prof pero vi茅ndolo bien si es el mismo concepto, ya que usa una variable del scope padre.

He querido aplicar DRY en el codigo, evitando la repeticion de la repetida al momento de obtener las peliculas por paginacion, lo he resuelto, aplicando objeto como parametro, el primero es la url o endpoint que solicitamos, el tercero, la query que deseamos consultar, bien sea el with_genres o el query comun y por defecto, para los trending le paso un null y por ultimo, hacia donde procede la solicitud, identificando esto, puedo adjuntarle a un objeto parameter que se le pasara a params en un llamado de axios, y alli, le agrego o el query o el with_genres, pense hacerlo con un Map pero como es un objeto que se crea en cada llamada, no lo vi tan necesario, espero tenga un buen perfomance y puedan ayudarme a mejorarlo

function getPaginatedMovies({url, query = undefined, searchBy = undefined}) {
  return async function () {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement

    const scrollIsBottom = scrollTop + clientHeight >= scrollHeight - 15
    const pageIsNotMax = page < maxPage

    const parameter = {
      page
    }

    if (searchBy == 'category') parameter.with_genres = query
    if (searchBy === 'search') parameter.query = query

    if (scrollIsBottom && pageIsNotMax) {
      page++

      const { data } = await axios_api(url, {
        params: parameter
      })


      const movies = data.results
      const options = {
        lazyload: true,
        cleanScreen: false,
      }

      createPreviewsMovies(movies, genericSection, options)
    }
  }
}

Quer铆a comentar, que agregu茅 un patr贸n Throttle para evitar la repetici贸n innecesaria de peticiones, durante el scrolling. Pueden consultar m谩s del tema aqu铆. Thottle y Debounce nos ayudan a gestionar mejor la ejecuci贸n de callbacks, y peticiones, en situaciones propensas a cuellos de botella. Adjunto el repositorio de mi c贸digo.

El infinite scrolling de la vista de pel铆culas de categor铆as me qued贸 as铆:

const throttleGetMoviesByCategory = throttle(getMoviesByCategory, 250);

const scrollingMoviesByCategory = async () => { 
    const { page } = categoriesHistory;

    if(scrollBottomReached()){
        await throttleGetMoviesByCategory({...categoriesHistory, page: page + 1 })
    }
}

En categoriesHistory solo alamcen茅 las variables de navegaci贸n y estado de la vista de pel铆culas por categor铆a:

    categoriesHistory = {
        categoryId,
        categoryName,
        page,
        total_pages,
    };

La funci贸n throttle es:

const throttle = (cb, delay = 500) => {
    let waiting = false;
    
    return async (...args) => {
        
        if (waiting) return;

        waiting = true;
 
        try {
            await cb(...args);
        }catch(err){
            console.log("Error: ", err);
        }

        await sleep(delay);
    
        waiting = false;
    }
}

y sleep, una funci贸n auxiliar para aplicar delay al c贸digo, que en este caso se usa para darle margen a la petici贸n (promesa) de completarse, sin que concurrentemente, se inicien nuevas peticiones al mismo URL, en este caso p谩ginaci贸n. (Misma p谩gina / datos redundantes).

const sleep = async (ms = 1000) => {
    return new Promise(resolve => setTimeout(resolve, ms));
}