Callbacks - Pasar Funciones como Props
En la clase anterior creamos el componente Pagination que recibe props con datos. Ahora vamos a aprender a pasar funciones como props para que el componente hijo pueda comunicarse con el padre y notificarle cuando algo cambia.
El problema: Componente sin interacción
Hasta ahora nuestro Pagination solo muestra información, pero no hace nada cuando haces click:
function Pagination({ currentPage = 1, totalPages = 5 }) {
const pages = Array.from({ length: totalPages }, (_, i) => i + 1)
return (
<nav className="pagination">
<a href="#">←</a>
{pages.map((page) => (
<a key={page} className={currentPage === page ? 'is-active' : ''} href="#">
{page}
</a>
))}
<a href="#">→</a>
</nav>
)
}
Problema: Cuando haces click en un botón, no pasa nada. ¿Cómo hacemos que el componente reaccione?
Pasando funciones como props
La solución es que el padre le pase una función al hijo mediante props:
// src/App.jsx
function App() {
const currentPage = 3
const totalPages = 5
const handlePageChange = (page) => {
console.log('Página cambiada a:', page)
}
return (
<>
<Header />
<main>
<SearchForm />
<JobListings />
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</main>
<Footer />
</>
)
}
export default App
¿Qué acabamos de hacer?
- Creamos una función
handlePageChangeen el padre - Pasamos esta función como prop
onPageChange={handlePageChange} - Ahora
Paginationtiene acceso a esta función
Convención: Las props que son funciones suelen empezar con on:
onClickonChangeonSubmitonPageChangeonDeleteonUpdate
Recibiendo y usando la función en el hijo
Ahora el hijo recibe la función y puede llamarla:
function Pagination({ currentPage = 1, totalPages = 5, onPageChange }) {
const pages = Array.from({ length: totalPages }, (_, i) => i + 1)
const handlePrevious = (e) => {
e.preventDefault()
if (currentPage > 1) {
onPageChange(currentPage - 1) // ← Llamamos a la función del padre
}
}
const handleNext = (e) => {
e.preventDefault()
if (currentPage < totalPages) {
onPageChange(currentPage + 1) // ← Llamamos a la función del padre
}
}
const handlePageClick = (e, page) => {
e.preventDefault()
onPageChange(page) // ← Llamamos a la función del padre
}
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} onClick={handlePrevious}>
<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="#"
onClick={(e) => handlePageClick(e, page)}
>
{page}
</a>
))}
<a href="#" style={styleLinkRight} onClick={handleNext}>
<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
¿Qué hace cada función?
handlePrevious
const handlePrevious = (e) => {
e.preventDefault() // Evita que el <a> navegue
if (currentPage > 1) {
onPageChange(currentPage - 1) // Notifica al padre
}
}
- Verifica que no estemos en la primera página
- Llama a
onPageChange(la función del padre) con la página anterior
handleNext
const handleNext = (e) => {
e.preventDefault()
if (currentPage < totalPages) {
onPageChange(currentPage + 1) // Notifica al padre
}
}
- Verifica que no estemos en la última página
- Llama a
onPageChangecon la página siguiente
handlePageClick
const handlePageClick = (e, page) => {
e.preventDefault()
onPageChange(page) // Notifica al padre con la página clickeada
}
- Llama a
onPageChangecon el número de página específico
¿Qué es un callback?
Un callback es una función que se pasa como argumento a otra función, y que será llamada más tarde (de ahí el nombre “call back” = “llamar de vuelta”).
// onPageChange es un callback
<Pagination onPageChange={handlePageChange} />
En este caso:
- El padre (
App) pasahandlePageChangecomo callback - El hijo (
Pagination) guarda este callback en la proponPageChange - Cuando el usuario hace click, el hijo “llama de vuelta” al padre ejecutando
onPageChange(page)
El flujo de comunicación
Ahora que tenemos el callback configurado, veamos qué pasa cuando el usuario interactúa:
1. Usuario hace click en "Página 3"
↓
2. Se dispara handlePageClick(e, 3) en Pagination
↓
3. handlePageClick llama a onPageChange(3)
↓
4. onPageChange es handlePageChange del padre (App)
↓
5. handlePageChange(3) ejecuta console.log('Página cambiada a:', 3)
Por ahora solo imprime en consola, pero en la próxima clase veremos cómo usar estado para que realmente cambie la página visualmente.
Convención de nombres
Es común usar estas convenciones para funciones:
En el componente padre
// Función que se pasa como prop: onAlgo
<Pagination onPageChange={handlePageChange} />
<Button onClick={handleClick} />
<Form onSubmit={handleSubmit} />
// Función que maneja el evento: handleAlgo
const handlePageChange = (page) => { ... }
const handleClick = () => { ... }
const handleSubmit = (data) => { ... }
En el componente hijo
// Recibir: onAlgo
function Pagination({ onPageChange }) {
// Función interna: handleAlgo
const handleNext = () => {
onPageChange(currentPage + 1)
}
}
Patrón:
on + Evento→ props que son funcioneshandle + Evento→ funciones que manejan eventos
Validando que la prop sea una función
Es buena práctica validar que las funciones prop realmente sean funciones:
function Pagination({ currentPage = 1, totalPages = 5, onPageChange }) {
// Validar que onPageChange sea una función
if (!onPageChange || typeof onPageChange !== 'function') {
console.warn('Pagination: onPageChange debe ser una función')
onPageChange = () => {} // Función vacía por defecto
}
// resto del código...
}
O podemos usar un valor por defecto:
function Pagination({ currentPage = 1, totalPages = 5, onPageChange = () => {} }) {
// Si no se pasa onPageChange, usa función vacía
// ...
}
Ejemplo adicional: Button con callback
Veamos otro ejemplo para reforzar el concepto:
// Componente Button reutilizable
function Button({ children, onClick, variant = 'primary' }) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{children}
</button>
)
}
// Uso en App
function App() {
const handleDelete = () => {
console.log('Eliminando...')
}
const handleSave = () => {
console.log('Guardando...')
}
return (
<div>
<Button onClick={handleSave}>Guardar</Button>
<Button onClick={handleDelete} variant="danger">
Eliminar
</Button>
</div>
)
}
El patrón es el mismo:
- El padre define funciones (
handleSave,handleDelete) - El padre pasa las funciones como props (
onClick={handleSave}) - El hijo recibe y ejecuta las funciones cuando ocurre el evento
Pasando argumentos a callbacks
A veces necesitas pasar información adicional:
function JobCard({ job, onApply }) {
return (
<article>
<h3>{job.title}</h3>
<button onClick={() => onApply(job.id)}>Aplicar</button>
</article>
)
}
function App() {
const handleApply = (jobId) => {
console.log('Aplicando a trabajo:', jobId)
}
return <JobCard job={job} onApply={handleApply} />
}
Nota: Usamos una arrow function onClick={() => onApply(job.id)} para poder pasar argumentos.
Diferencia: onClick vs onClick()
Es importante entender esta diferencia:
// ✅ Correcto: Pasa la función
<button onClick={handleClick}>Click</button>
// ❌ Incorrecto: EJECUTA la función inmediatamente
<button onClick={handleClick()}>Click</button>
// ✅ Correcto: Arrow function para pasar argumentos
<button onClick={() => handleClick(id)}>Click</button>
// ✅ Correcto: Pasar referencia directa sin argumentos
<button onClick={handleClick}>Click</button>
Regla:
onClick={handleClick}→ Pasa la referencia a la funciónonClick={handleClick()}→ Ejecuta la función inmediatamente (¡no queremos esto!)onClick={() => handleClick(arg)}→ Crea una nueva función que ejecutaráhandleClickcon argumentos
¡Resumiendo que es gerundio!
En esta clase has aprendido:
- 🔄 Callbacks - Funciones pasadas como props
- 📤 Comunicación hijo → padre - El hijo llama a funciones del padre
- 🎯 Convención on/handle -
onEventpara props,handleEventpara funciones - ✅ Validación - Verificar que las props sean funciones
- 🔀 Flujo de comunicación - El hijo notifica al padre mediante callbacks
- ⚡ onClick vs onClick() - Diferencia crucial entre referencia y ejecución
- 📝 Ejemplo práctico - Button reutilizable con onClick
- 🎯 Pasar argumentos - Usar arrow functions para pasar datos
En la próxima clase completaremos el ciclo aprendiendo sobre estado en el componente padre, haciendo que los cambios se reflejen visualmente en la interfaz.
💡 Recuerda: Las funciones como props (callbacks) permiten que los componentes hijos notifiquen a sus padres sobre eventos. El hijo no modifica datos directamente, solo informa al padre que algo ocurrió llamando a la función callback.