Cómo hacer un carrusel con React, desde 0

Gabriel Jiménez | Hace alrededor de 2 meses

Antes de comenzar a implementar un carrusel, es importante saber qué es y cuando conviene utilizarlo. Partiendo de ahí, el siguiente paso es pensar en su diseño, ya que todo diseño debería partir de una buena planificación. Con el diseño en mano, pasaremos a la implementación, donde aprendemos no solo a construir el carrusel desde cero, sino también a aplicar buenas prácticas en React. Finalmente, revisaremos algunas alternativas en el mercado, por si prefieres ahorra tiempo y esfuerzo.


¿Qué es un carrusel?

Un carrusel es un elemento de interfaz que permite mostrar contenido en forma de diapositiva. Es decir, el usuario ve un elemento a la vez y puede avanzar o retroceder para explorar el resto del contenido.


Para visualizarlo mejor, revisemos la figura 1.

Figura 1. ¿Qué es un carrusel? Un carrusel puede entenderse como una subasta de arte, donde cada pieza se presenta de forma individual. De la misma forma, el carrusel muestra cada elemento uno por uno.


¿Cuándo usar un carrusel?

Un carrusel es útil cuando necesitamos listar contenido de forma dinámica y secuencial. Por ejemplo:


  • Mostrar banners promocionales en la página principal del un sitio.
  • Exhibir una galería de imágenes, ya se de productos, eventos o portafolios.
  • Destacar detalles de un producto, mostrar dinamitas vistas o características.
  • Guiar paso a paso un proceso o resultado, permitiendo que el usuario avance de forma ordenada.
  • Ofertas o promociones limitadas.
  • Testimonios de clientes.
  • Noticias destacadas o artículos recientes.

Características principales de un carrusel

Con lo visto anteriormente, podemos identificar las características principales de un carrusel. Entre ellas destacan:


Diapositivas. Cada diapositiva permite mostrar un contenido específico, como una imagen, texto o tarjeta.

Controles. Elemento que permiten avanzar o retroceder entre las diapositivas. Generalmente, las vemos como botones o flechas.

Indicadores. Marcadores que ayudan a saber cuántas diapositivas existen y en cuál nos encontramos.

Titulo y Subtítulos. Texto opcional que acompaña a cada diapositiva para dar más contexto al contenido mostrado.


Cómo hacer un carrusel básico en React

Ya sabemos qué, cuándo conviene utilizarlo y las características principales que lo componente. Ahora toca ponernos creativos y diseñar una solución que podamos implementar paso a paso en React.


Diseño

El diseño es uno de los procesos más sobrevalorados en el desarrollo de software, ya sea backend o frontend. Sin embargo, es uno de los más confiables para crear soluciones escalables. 


Dicho esto, analicemos la figura 2.

Figura 2. Propuesta de diseño de un Carrusel. El componente Carrusel sincroniza la comunicación y mantiene el estado (indice actual). Control e Indicators emiten cambios de navegación/selección y Slide renderiza la diapositiva activa.


El flujo principal que vemos en la imagen es el siguiente:
  1. El usuario cambia de diapositiva usando Control
  2. Control notifica sobre el cambio de diapositiva al Carrusel.
  3. El Carrusel actualiza su estado interno de la diapositiva activa.
  4. El Carrusel notifica a Slide para renderizar la nueva diapositiva.
  5. El Carrusel notifica a Indicators para resaltar la diapositiva activa y mostrar el total.
  6. El Carrusel notifica a Control para deshabilitar botones si ya no hay más diapositivas.
  7. El usuario cambia de diapositiva usando el Indicators.
  8. Indicators notifica al Carrusel.
  9. El Carrusel actualiza su estado interno.
  10. El Carrusel notifica a Slide para renderizar la nueva diapositiva.

En resumen, cada componente tiene una responsabilidad específica.

Responsabilidades

Cada componente es encargado de realizar una tarea específica. De esta forma, mantenemos el componente bien estructurado.


  • Carrusel. Gestiona la comunicación entre todos y mantiene un estado interno de la diapositiva activa.
  • Slide. Renderizar la diapositiva activa. Opcionalmente puede recibir título y subtítulo.
  • Control. Permite avanzar o retroceder y emite un evento para notificar al Carrusel del cambio.
  • Indicators. Igual que Control, permite navegar. Además, lista cuántas dispositivas hay y, usando el estado del Carrusel, muestra en qué diapositiva estamos.

Implementación

Bla, bla, bla… Muéstrame código… Lo sé, el diseño puede ser un proceso un poco tardado. Sin embargo, prácticamente ya hemos desarrollado casi el 80 % del componente y el 20 % es solo tirar código. Vayamos allá y veamos cómo implementarlo.


Comencemos definiendo cada uno de los componentes:

src/components/Carrusel

    1: import React from 'react'
    2: 
    3: const Carrusel = () => {
    4:   return(<div className='carrusel'></div>)
    5: }
    6: 
    7: export const Slide = () => {
    8:   return(<div className='slide'></div>)
    9: }
   10: 
   11: export const Control = () => {
   12:   return(<div className='control'></div>)
   13: }
   14: 
   15: export const Indicators = () => {
   16:   return(<div className='indicators'></div>)
   17: }
   18: 
   19: export default Carrusel;


Es bastante sencillo: agregamos una clase CSS a cada componente para al final darle estilos.

Ahora vamos a atacar cada uno de los componentes de forma individual.


Componente Indicators

De forma muy general, el componente permite cambiar de diapositiva. Además, visualiza el total de diapositivas y destaca la activa.

src/components/Carrusel

. . .
   44: export const Indicators = ({ slides , current , onChange }) => {
   45:   return (
   46:     <div>
   47:       {slides.map((_, i) => (
   48:         <button
   49:           key={i}
   50:           onClick={() => onChange && onChange(i)}
   51:         >
   52:           {i === current ? "●" : "○"}
   53:         </button>
   54:       ))}
   55:     </div>
   56:   )
   57: }
. . .


Analicemos


Línea 45-56

Renderizamos el total de diapositivas y asociamos un evento click.


Línea 50

Notifica a Carrusel sobre el cambio de diapositiva.


NOTA: Si quieres aprender más sobre props, te recomiendo mi artículo: React props: buenas prácticas para código más limpio y escalable.


Continuemos con el siguiente componente.




Componente Control

Su responsabilidades es bastante sencilla: permitir cambiar de diapositiva y bloquear en caso de que no existan más.

src/components/Carrusel

. . .
   17: export const Control = ({ slides, current, onChange }) => {
   18:   const totalSlides = slides.length
   19: 
   20:   function next() {
   21:     if (totalSlides === 0) return
   22:     const target = current + 1
   23:     if (target < totalSlides && onChange) onChange(target)
   24:   }
   25: 
   26:   function prev() {
   27:     if (totalSlides === 0) return
   28:     const target = current - 1
   29:     if (target >= 0 && onChange) onChange(target)
   30:   }
   31: 
   32:   return (
   33:     <div className="control">
   34:       <button onClick={prev} disabled={current <= 0}>
   35:         Anterior
   36:       </button>
   37:       <button onClick={next} disabled={current >= totalSlides - 1}>
   38:         Siguiente
   39:       </button>
   40:     </div>
   41:   )
   42: }
. . .


Siguiente componente.


Componente Slide

Debe mostrar el contenido de la diapositiva activa. Además, opcionalmente puede mostrar un título y/o subtítulo.

src/components/Carrusel

. . .
    7: export const Slide = ({ content, title, subtitle }) => {
    8:   return (
    9:     <div className="slide">
   10:       {title && <h3>{title}</h3>}
   11:       {subtitle && <p>{subtitle}</p>}
   12:       {content}
   13:     </div>
   14:   )
   15: }
. . .


Listo.

Ahora solo falta crear el Carrusel.


Componente Carrusel

Es el encargado de coordinar la comunicación entre cada uno de los componente. Además, mantiene un estado de interno para saber cuál es la diapositiva activa.


Entre otras de sus tareas importantes está:


  • Renderizar únicamente los componentes Control, Indicators y Slide.
  • Colocar cada tipo de componente en la posición que le corresponde dentro del Carrursel.

Veamos la implementación:

src/components/Carrusel

. . .
    3: const Carrusel = ({ children }) => {
    4:   let control = null
    5:   let slides = []
    6:   let indicators = null
    7: 
    8:   const items = Children.toArray(children)
    9: 
   10:   items.forEach((child) => {
   11:     if (!React.isValidElement(child)) {
   12:       throw new Error("Carrusel solo puede renderizar componentes válidos.")
   13:     }
   14: 
   15:     switch (child.type) {
   16:       case Control:
   17:         control = child
   18:         break
   19:       case Slide:
   20:         slides.push(child)
   21:         break
   22:       case Indicators:
   23:         indicators = child
   24:         break
   25:       default:
   26:         throw new Error(
   27:           "NavBar solo puede renderizar hijos de tipo <Control />, <Slide />, o <Indicators />."
   28:         )
   29:     }
   30:   })
   31: 
   32:   const [currentSlide, setCurrentSlide] = useState(0)
   33: 
   34:   function handleCurrentSlide(index) {
   35:     setCurrentSlide(index)
   36:   }
   37: 
   38:   return(
   39:     <div className='carrusel'>
   40:       {cloneElement(control, {
   41:         slides: slides,
   42:         current: currentSlide,
   43:         onChange: handleCurrentSlide
   44:       })}
   45:       {slides.map((slide, index) => index === currentSlide ? slide : null)}
   46:       {
   47:         cloneElement(indicators, {
   48:           slides: slides,
   49:           current: currentSlide,
   50:           onChange: handleCurrentSlide
   51:         })
   52:       }
   53:     </div>
   54:   )
   55: }
. . .


Analicemos


Línea 4-30

Filtramos solo componentes de Control, Indicators y Slide. Solo debe existir un componente Control y Indicators, mientras que el Slide permite un arreglo.


Línea 32

Estado interno para mantener la diapositiva activa.


Línea 38-54

Renderizamos los componentes en una posición en particular.


Línea 40-44

Clonamos Control y le pasamos los props  necesarios para su funcionamiento: slides, currentSlide y un callback para saber si internamente se ha modificado.


Línea 47-52

De igual manera que Control, clonamos Indicators y le pasamos los mismos props.


La forma de utilizarlo sería la siguiente:

// Con Control, Slides y Indicators.
<Carrusel>
  <Control/>
  <Slide content='Slide 1'/>
  <Slide content='Slide 2'/>
  <Slide content='Slide 3'/>
  <Indicators/>
</Carrusel>

// Sin Control
<Carrusel>
  <Slide content='Slide 1'/>
  <Slide content='Slide 2'/>
  <Slide content='Slide 3'/>
  <Indicators/>
</Carrusel>

// Indicators.
<Carrusel>
  <Control/>
  <Slide content='Slide 1'/>
  <Slide content='Slide 2'/>
  <Slide content='Slide 3'/>
</Carrusel>


Como un plus podríamos agregar un componente Slides donde agrupe los Slide, de esta forma el componente se vería más limpio y estructurado. Gracias a las responsabilidades bien definidas de cada componente, hacer un cambio es relativamente sencillo.

Alternativas y librerías recomendadas

En el mercado ya existen muchas librerías que cumplen con este objetivo. Sin embargo, siempre es importante tener noción sobre como funcionan las cosas, tal vez no tan técnicamente pero si de forma general. 


A continuación te comparto varias alternativas.


Joy UI

Joy UI es una librería basada en Material UI, pero con un estilo propio.


NOTA: Material UI ofrece un set de componentes React ya listos para usarse en cualquier proyecto.


Entre de Joy UI encontramos una versión muy básica de un Carrusel. Para que se entienda mejor, veamos la figura 3.

Figura 3. Carrusel con Joy UI. Ejemplo de una implementación básica del componente Carrusel.


Si quieres probar esta librería te dejo el enlace: https://mui.com/joy-ui/react-aspect-ratio/#mobile-carousel

Bootstrap

La librería de cajón que todo desarrollador frontend ha escuchado. Compuesta de varios componentes en Javascript, flexibles, fáciles de configurar y escalables.


En este caso, tenemos el componente Carrusel — no como el caso anterior, donde es un ejemplo muy básico.


Su implementación es bastante sencilla, veamos la figura 4.

Figura 4. Carrusel con Bootstrap. Ejemplo de un Carrusel usando Bootstrap, adaptable también a React.


Si quieres conocer más sobre el componente Carrusel usando Bootstrap, haz clic.

Conclusión

Al dividir el Carrusel en subcomponentes como Control, Slide e Indicators, tu código se vuelve más claro, escalable y fácil de mantener. Esto te da una base sólida para después agregar estilos, personalizar la experiencia en móviles y extender la funcionalidad sin problemas.


Además, construir un Carrusel en React desde cero no solo es un buen ejercicio para entender cómo funciona la composición de componentes, también te permite crear soluciones a medida de tu aplicación.