hand Inicio

Crear un scroll infinito con React y un custom hook

Se trata de cargar nuevos elementos en respuesta a un evento, y la manera más cómoda de hacerlo es mediante un{' '} hook

Cargar datos a medida que el usuario lo pide, bien apretando un botón o bien cuando ocurra un evento de scroll es una manera excelente de evitar sobrecargar nuestro JS Bundle y el número de nodos DOM a procesar, algo que siempre tiene un impacto alto en los score de Lighthouse (y todo lo que ello implica para el posicionamiento web)

Por lo tanto, si nuestro caso aplica (no hay nada peor que optimizar cuando no es necesario), implementar un infinite scroll es el camino a seguir

Opciones varias

Tenemos muchas opciones para implementar esto

La posiblemente mejor (por aquello de no reinventar la rueda y utilizar soluciones muy probadas y sólidas): utilizar librerías externas

Por ejemplo

Pero si no nos apetece, siempre podemos hacerlo nosotros mismos

Utilizando un hook

Un hook no es más que una manera de estructurar una función que nos facilita su encapsulamiento y su reutilización (en un contexto de React)

El hook es el envoltorio, pero de qué irá el contenido?

Un infinite scrolling se refiere simplemente al hecho de cargar elementos en la medida en que los necesitemos, ya sea porque el usuario se está acercando al último elemento visible, o porque se aprieta un botón de "más entradas"

Para usos extremos no sólo necesitaríamos ir añadiendo esos nuevos elementos sino que también sería crítico ir eliminando los elementos primeros (que ya están fuera de la visión del usuario), para evitar sobrecargar el DOM y que la página se vaya volviendo más y más pesada

Pero si nuestro contenedor no espera miles de elementos esta optimización es innecesaria

En este caso, pediremos una nueva imagen de picsum.photos, haremos bloques de 10 y al final un botón para pedir otro bloque de 10, y así succesivamente

Estos bloques podrían ser artículos, temas, herramientas, y podríamos ya tener un número total de elementos

Una implementación simple sería la siguiente, sin hook

jsx
import React, { useState, useEffect } from 'react'

export default function App() {
const [images, setImages] = useState([])
const [blocks, setBlocks] = useState(1)

const more = async () => {
const more_images = await fetch_images(blocks + 1)
setBlocks(prev => prev + 1)
setImages(prev => [...prev, ...more_images])
}

const fetch_images = async page => (await fetch(`https://picsum.photos/v2/list?page=${page}&limit=10`)).json()

useEffect(() => {
;(async () => {
const picsum = await fetch_images(0)
setImages(picsum)
})()
}, [])

return (
<div className="App">
<div style={{ display: 'flex', flexDirection: 'column' }}>
{images.map((el, i) => (
<img
key={`img${i}`}
src={el.download_url}
style={{
width: '150px',
height: 'auto',
marginBottom: '10px',
borderRadius: '3px',
}}

alt=""
/>

))}
</div>
<button style={{ marginBottom: '100px' }} onClick={more}>
more images
</button>
</div>
)
}

Aquí cada vez que apretamos el botón pedimos nuevas imágenes a picsum y se las añadimos al array, guardado en el state images, y listos

Si en lugar de apretar un botón queremos que sea automático al pasar por un elemento determinado (por ejemplo el penúltimo del array), yo lo haría con la librería react-intersection-observer

Pero sin entrar en esto, la gracia ahora es convertir el código de antes en un hook

Qué problema nos solucionaría un hook?

Básicamente simplificar el código, encapsularlo y sacarlo de aquí, porque si queremos utilizar la misma lógica en otro componente tendremos que replicar todas las funciones y contaminar ese código, algo que nunca será buena idea

Con el hook lo que queremos es una función que nos devuelva lo que necesitemos, y listos, y en este caso lo suyo sería que nos devolviese el array de imágenes, y la función more()

jsx
import React, { useState, useEffect } from 'react'

export default function App() {
const [images, more] = useInfinitePicsum()

return (
<div className="App">
<div style={{ display: 'flex', flexDirection: 'column' }}>
{images.map((el, i) => (
<img
key={`img${i}`}
src={el.download_url}
style={{
width: '150px',
height: 'auto',
marginBottom: '10px',
borderRadius: '3px',
}}

alt=""
/>

))}
</div>
<button style={{ marginBottom: '100px' }} onClick={more}>
more images
</button>
</div>
)
}

const useInfinitePicsum = () => {
// estado para almacenar las imágenes
const [images, setImages] = useState([])
// estado para contar el número de bloques que pido, lo necesito para que picsum no me devuelva siempre las mismas imágenes
const [blocks, setBlocks] = useState(1)

// función para pedir más imágenes
const more = async () => {
const more_images = await fetch_images(blocks + 1)
setBlocks(prev => prev + 1)
setImages(prev => [...prev, ...more_images])
}

// función para pedir (esta vez literalmente) las imagenes a picsum
const fetch_images = async page => (await fetch(`https://picsum.photos/v2/list?page=${page}&limit=10`)).json()

// useEffect para ejecutar al principio la primera "pedida" de imágenes
useEffect(() => {
;(async () => {
const picsum = await fetch_images(0)
setImages(picsum)
})()
}, [])

return [images, more]
}

Con esto la lógica React de la función principal es muy fácil de entender, y la lógica del hook es la que encapsula todo lo que viene a ser el crear el infinite scroll

Listos!

🙋‍♂️

draw of me

Hola, tienes en mente desarrollar una web?

Si quieres, te ayudo

Escríbeme