Paginación - Props y Comunicación Padre-Hijo

En esta clase vamos a crear un componente Pagination que nos permitirá navegar entre diferentes páginas de contenido. Aprenderemos a pasar datos mediante props, definir valores por defecto, y lo más importante: cómo un componente hijo puede comunicarse con su padre usando funciones.

Creando el componente Pagination

Empezaremos creando un componente simple que recibe la página actual y el total de páginas:

// src/components/Pagination.jsx
function Pagination({ currentPage, totalPages }) {
  // Generar array de páginas a mostrar
  const pages = Array.from({ length: totalPages }, (_, i) => i + 1)

  const styleLinkLeft = {
    opacity: currentPage === 1 ? 0.5 : 1,
    cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
  }

  const styleLinkRight = {
    opacity: currentPage === totalPages ? 0.5 : 1,
    cursor: currentPage === totalPages ? 'not-allowed' : 'pointer',
  }

  return (
    <nav className="pagination">
      <a href="#" style={styleLinkLeft}>
        <svg
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
        >
          <path stroke="none" d="M0 0h24v24H0z" fill="none" />
          <path d="M15 6l-6 6l6 6" />
        </svg>
      </a>

      {pages.map((page) => (
        <a key={page} className={currentPage === page ? 'is-active' : ''} href="#">
          {page}
        </a>
      ))}

      <a href="#" style={styleLinkRight}>
        <svg
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="1.5"
          strokeLinecap="round"
          strokeLinejoin="round"
        >
          <path stroke="none" d="M0 0h24v24H0z" fill="none" />
          <path d="M9 6l6 6l-6 6" />
        </svg>
      </a>
    </nav>
  )
}

export default Pagination

Generando el array de páginas

const pages = Array.from({ length: totalPages }, (_, i) => i + 1)

¿Qué hace esto?

  • Array.from() crea un nuevo array
  • { length: totalPages } define la longitud del array
  • (_, i) => i + 1 transforma cada índice en un número de página

Ejemplo:

// Si totalPages = 5
Array.from({ length: 5 }, (_, i) => i + 1)
// Resultado: [1, 2, 3, 4, 5]

Renderizando listas con .map()

{
  pages.map((page) => (
    <a key={page} className={currentPage === page ? 'is-active' : ''} href="#">
      {page}
    </a>
  ))
}

¿Qué hace .map()?

  • Recorre cada elemento del array pages
  • Por cada page, retorna un elemento <a>
  • key={page} es obligatorio para que React identifique cada elemento

💡 Nota: Profundizaremos en renderizado de listas en clases futuras. Por ahora, entiende que .map() transforma un array de datos en un array de elementos JSX.

Props por defecto

¿Qué pasa si no pasamos las props al componente? Se rompe. Vamos a definir valores por defecto:

❌ Forma tradicional (menos recomendada)

function Pagination({ currentPage, totalPages }) {
  currentPage = currentPage || 1
  totalPages = totalPages || 5

  // resto del código...
}

Problema: Es verboso y no es la forma idiomática de JavaScript moderno.

✅ Forma moderna (recomendada)

function Pagination({ currentPage = 1, totalPages = 5 }) {
  // Si no se pasan props, usa los valores por defecto
  // resto del código...
}

Ventajas:

  • ✅ Más limpio y conciso
  • ✅ Valores por defecto en la firma de la función
  • ✅ Es la sintaxis estándar de ES6

Ejemplo de uso:

// Sin props: usa valores por defecto
<Pagination />
// currentPage = 1, totalPages = 5

// Con una prop: usa el valor pasado + defecto para la otra
<Pagination currentPage={3} />
// currentPage = 3, totalPages = 5

// Con ambas props: usa los valores pasados
<Pagination currentPage={3} totalPages={10} />
// currentPage = 3, totalPages = 10

Usando el componente en App

Ahora vamos a usar nuestro componente Pagination en App.jsx:

// src/App.jsx
import { Header } from './components/Header'
import { Footer } from './components/Footer'
import { SearchForm } from './components/SearchForm'
import { JobListings } from './components/JobListings'
import Pagination from './components/Pagination'

function App() {
  const currentPage = 3
  const totalPages = 5

  return (
    <>
      <Header />

      <main>
        <SearchForm />
        <JobListings />
        <Pagination currentPage={currentPage} totalPages={totalPages} />
      </main>

      <Footer />
    </>
  )
}

export default App

¿Qué está pasando?

  1. Definimos currentPage y totalPages en el componente padre (App)
  2. Pasamos estos valores como props al componente hijo (Pagination)
  3. Pagination recibe las props y las usa para renderizarse

Flujo de datos:

App (padre)

  ├─ currentPage: 3
  ├─ totalPages: 5


Pagination (hijo)

  └─ Renderiza botones según estos valores

Estilos inline con objetos

Nota que usamos estilos inline en el componente:

const styleLinkLeft = {
  opacity: currentPage === 1 ? 0.5 : 1,
  cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
}

Características de estilos inline en React:

  • Se pasan como objetos JavaScript
  • Las propiedades CSS usan camelCase: background-colorbackgroundColor
  • Los valores pueden ser dinámicos (cambian según props o estado)
<a href="#" style={styleLinkLeft}>

Equivalente en HTML:

<a href="#" style="opacity: 0.5; cursor: not-allowed;"></a>

Prevenir comportamiento por defecto

Nota el e.preventDefault() en todas las funciones:

const handlePrevious = (e) => {
  e.preventDefault() // ← Importante
  // ...
}

¿Por qué?

  • Los enlaces <a href="#"> por defecto navegan a #
  • Esto causa un pequeño salto en la página
  • preventDefault() cancela este comportamiento
  • Solo ejecuta nuestra lógica personalizada

Convención de nombres para event handlers

Es común usar estas convenciones:

// Función que se pasa como prop: onAlgo
<Pagination onPageChange={handlePageChange} />

// Función dentro del componente: handleAlgo
const handleNext = () => { ... }
const handlePrevious = () => { ... }
const handlePageClick = () => { ... }

Patrón:

  • on + Evento para props que son funciones
  • handle + Evento para funciones que manejan eventos

¡Resumiendo que es gerundio!

En esta clase has aprendido:

  • 🧩 Componente Pagination - Crear un componente de navegación entre páginas
  • 📊 Array.from() - Generar arrays de números
  • 🔄 Renderizado de listas - Usar .map() para renderizar elementos (básico)
  • 🎯 Props por defecto - Sintaxis { prop = valorDefecto }
  • 🎨 Estilos inline - Objetos JavaScript para estilos dinámicos
  • 🚫 preventDefault() - Evitar comportamiento por defecto de eventos
  • 📛 Convención de nombres - on + Evento para props y handle + Evento para funciones

En la próxima clase aprenderemos sobre callbacks y cómo pasar funciones como props para que los componentes hijos puedan comunicarse con sus padres.

💡 Recuerda: Las props fluyen de padre a hijo. Los estilos inline se pasan como objetos JavaScript con propiedades en camelCase. Usa preventDefault() para evitar comportamientos por defecto del navegador.