Llevas dos lecciones arrastrando la misma limitación: el catálogo de CicloUrbano muestra tres tarjetas idénticas porque los datos de la bicicleta están escritos dentro de TarjetaBicicleta. Un componente así no es reutilizable: solo sirve para mostrar esa bicicleta concreta. Las props son la solución, y son uno de los dos o tres conceptos verdaderamente fundamentales de React. Una prop es un dato que un componente recibe desde fuera, de quien lo usa. Convierten un componente rígido en una plantilla parametrizable, definen el contrato entre padre e hijo y son la razón por la que puedes escribir TarjetaBicicleta una vez y usarla en cinco pantallas distintas. En esta lección aprenderás a pasarlas y recibirlas, qué tipos de valores admiten, cómo darles valores por defecto, qué es la prop especial children, por qué son de solo lectura y cómo documentar el contrato de tus componentes. Al terminar, el catálogo mostrará por fin bicicletas distintas.
Contenido
- Qué son las props y qué problema resuelven
- Pasar props y recibirlas
- Destructuring en la firma del componente
- Qué tipos de valores se pueden pasar
- Valores por defecto
- La prop especial
children - Las props son de solo lectura
- Difundir props con el operador de propagación
- Documentar el contrato de un componente
- El catálogo de CicloUrbano, ya parametrizado
- Qué son las props y qué problema resuelven
Props viene de properties. Son los datos que fluyen desde un componente padre hacia un componente hijo a través de los atributos del JSX.
La analogía más útil es la de una función: un componente es una función, y las props son sus argumentos.
// Una función sin parámetros solo sabe hacer una cosa
function saludar() {
return 'Hola, Ana Ribera';
}
// Una función con parámetros sirve para infinitos casos
function saludar(nombre) {
return `Hola, ${nombre}`;
}Exactamente lo mismo pasa con los componentes:
// Componente sin props: solo sabe mostrar una bicicleta
function TarjetaBicicleta() {
return <h3>Urbana Clásica</h3>;
}
// Componente con props: sirve para cualquier bicicleta
function TarjetaBicicleta(props) {
return <h3>{props.modelo}</h3>;
}La analogía no es casual: las props son literalmente los argumentos de la función componente. React recoge los atributos que escribes en el JSX, los mete en un objeto y le pasa ese objeto a tu función como primer parámetro.
flowchart LR
A["App<br/>escribe el JSX"] -- "modelo='Urbana Clásica'" --> B["React<br/>construye { modelo: 'Urbana Clásica' }"]
B -- "lo pasa como argumento" --> C["TarjetaBicicleta(props)<br/>lo usa en su JSX"]
Y una propiedad estructural muy importante: el flujo es unidireccional y desciende por el árbol. Un padre pasa datos a un hijo; un hijo nunca «devuelve» datos escribiendo sobre sus props. Esa restricción, que al principio parece limitante, es justo lo que hace predecible una aplicación React: para saber de dónde sale un dato, subes por el árbol; nunca tienes que buscar quién lo modificó por sorpresa.
- Pasar props y recibirlas
Pasarlas: atributos en el JSX
Dos sintaxis conviven, y la diferencia es la que ya viste en la lección de JSX (01-04):
| Sintaxis | Cuándo se usa | Ejemplo |
|---|---|---|
| Comillas | Solo para cadenas de texto literales | modelo="Urbana Clásica" |
| Llaves | Para cualquier otra cosa: números, booleanos, objetos, arrays, variables, expresiones | precioHora={2.5} |
Un error clásico: precioHora="2.5" pasa la cadena "2.5", no el número. Y "2.5".toFixed(2) no existe, así que la tarjeta reventará. Si el valor no es texto, va entre llaves.
Recibirlas: el parámetro props
// src/componentes/TarjetaBicicleta.jsx
function TarjetaBicicleta(props) {
return (
<article className="tarjeta-bicicleta">
<h3>{props.modelo}</h3>
<p>
<strong>{props.precioHora.toFixed(2)} € / hora</strong>
</p>
</article>
);
}
export default TarjetaBicicleta;React llama a tu función con un único objeto que contiene todos los atributos que escribiste:
// Lo que React construye internamente y pasa a la función
{ modelo: 'Urbana Clásica', precioHora: 2.5 }Tres detalles que conviene fijar desde el principio:
- El parámetro puede llamarse como quieras.
propses la convención universal, pero es un parámetro normal. Llámaloprops. - Los nombres de las props los eliges tú. No hay una lista de props válidas: son el vocabulario de tu componente. Usa camelCase (
precioHora,estacionId), igual que en JavaScript. - Si no pasas un atributo, esa prop vale
undefined. No da error; simplemente no está. Por eso más adelante veremos los valores por defecto.
- Destructuring en la firma del componente
Escribir props. delante de cada valor es ruidoso. El destructuring de ES6 permite extraer las props directamente en la firma de la función, y es la forma dominante en React moderno.
// Con props.
function TarjetaBicicleta(props) {
return (
<article className="tarjeta-bicicleta">
<h3>{props.modelo}</h3>
<p>Tipo: {props.tipo}</p>
<p>Estado: {props.estado}</p>
</article>
);
}// Con destructuring en la firma <- forma recomendada
function TarjetaBicicleta({ modelo, tipo, estado }) {
return (
<article className="tarjeta-bicicleta">
<h3>{modelo}</h3>
<p>Tipo: {tipo}</p>
<p>Estado: {estado}</p>
</article>
);
}Las llaves de ({ modelo, tipo, estado }) no son un objeto: son la sintaxis de desestructuración aplicada al parámetro. Es equivalente a escribir const { modelo, tipo, estado } = props; en la primera línea del cuerpo.
Por qué es mejor:
| Ventaja | Explicación |
|---|---|
| Contrato visible | La firma dice exactamente qué espera el componente. Se lee como una documentación |
| Menos ruido | Desaparece el prefijo props. de todo el JSX |
| Errores antes | Si escribes mal un nombre en la firma, lo detectas en el sitio donde importa |
| Valores por defecto sencillos | Se escriben ahí mismo, como verás en el apartado 5 |
Destructuring anidado y parcial
En CicloUrbano vamos a pasar la bicicleta entera como un objeto, no campo a campo. Se puede destructurar en dos niveles:
// Recibimos la prop 'bicicleta' y la usamos como objeto
function TarjetaBicicleta({ bicicleta }) {
return <h3>{bicicleta.modelo}</h3>;
}
// O extraemos también sus campos (destructuring anidado)
function TarjetaBicicleta({ bicicleta: { modelo, tipo, estado } }) {
return <h3>{modelo}</h3>;
}La segunda forma es correcta, pero pierde la referencia al objeto completo y se vuelve ilegible con muchos campos. En el curso usaremos la primera: recibimos bicicleta y, si hace falta, la destructuramos en el cuerpo.
- Qué tipos de valores se pueden pasar
Cualquier valor de JavaScript. Literalmente cualquiera.
| Tipo | Cómo se pasa | Ejemplo en CicloUrbano |
|---|---|---|
| Cadena | Comillas o llaves | modelo="Urbana Clásica" |
| Número | Siempre llaves | precioHora={2.5} |
| Booleano | Llaves, o el atajo del atributo suelto | destacada={true} o destacada |
| Objeto | Llaves (dobles si es un literal) | bicicleta={bicicletas[0]} · bicicleta={{ id: 'bici-001' }} |
| Array | Llaves | tipos={['urbana', 'electrica']} |
| Función | Llaves, sin paréntesis | alReservar={reservarBicicleta} |
| Elemento JSX | Llaves | icono={<EtiquetaEstado estado="disponible" />} |
null / undefined |
Llaves | estacion={null} |
Veamos los casos que más se prestan a confusión.
Booleanos y el atajo
<TarjetaBicicleta bicicleta={bici} destacada={true} />
<TarjetaBicicleta bicicleta={bici} destacada /> {/* Idéntico al anterior */}
<TarjetaBicicleta bicicleta={bici} destacada={false} />
<TarjetaBicicleta bicicleta={bici} /> {/* destacada es undefined, no false */}Escribir el atributo suelto equivale a ={true}, igual que en HTML con disabled o checked. Ojo con la última línea: omitir la prop da undefined, que es falsy pero no es false. En la práctica ambos se comportan igual en una condición, pero no son el mismo valor.
Objetos y la doble llave
{/* Pasamos una variable que ya es un objeto: unas llaves */}
<TarjetaBicicleta bicicleta={bicicletas[0]} />
{/* Escribimos el objeto ahí mismo: llaves de la expresión + llaves del objeto */}
<TarjetaBicicleta bicicleta={{ id: 'bici-001', modelo: 'Urbana Clásica' }} />Es la misma doble llave que ya viste en style={{ color: 'red' }}: las de fuera abren una expresión JSX, las de dentro son el literal de objeto.
Funciones: sin paréntesis
{/* CORRECTO: pasamos la función */}
<TarjetaBicicleta bicicleta={bici} alReservar={reservarBicicleta} />
{/* INCORRECTO: la ejecutamos ahora y pasamos su resultado */}
<TarjetaBicicleta bicicleta={bici} alReservar={reservarBicicleta()} />Pasar funciones como props es el mecanismo con el que un hijo avisa a su padre de que algo ha ocurrido. Aquí solo necesitas saber que se puede y que la sintaxis es esta; el patrón completo de comunicación hijo → padre se estudia en Elevando el Estado, y los manejadores de eventos en Manejo de Eventos.
Elementos JSX como props
Una prop puede contener interfaz. Esto abre la puerta a componentes muy flexibles:
- Valores por defecto
Si el padre no pasa una prop, esta llega como undefined. Para que el componente siga funcionando, se le dan valores por defecto con los parámetros por defecto de JavaScript, directamente en la desestructuración:
// src/componentes/EtiquetaEstado.jsx
function EtiquetaEstado({ estado = 'disponible', compacta = false }) {
const clases = compacta
? `estado estado--${estado} estado--compacta`
: `estado estado--${estado}`;
return <span className={clases}>{estado}</span>;
}
export default EtiquetaEstado;<EtiquetaEstado estado="alquilada" /> {/* muestra 'alquilada', no compacta */}
<EtiquetaEstado /> {/* muestra 'disponible' por defecto */}
<EtiquetaEstado estado="carga" compacta />Un matiz que causa sorpresas: el valor por defecto se aplica solo si la prop es undefined, no si es null, 0 o cadena vacía.
<EtiquetaEstado estado={null} /> {/* estado vale null, NO 'disponible' */}
<EtiquetaEstado estado="" /> {/* estado vale '', NO 'disponible' */}Nota sobre defaultProps
En código antiguo verás esta otra forma:
// FORMA ANTIGUA — no la uses en componentes de función
EtiquetaEstado.defaultProps = {
estado: 'disponible'
};defaultProps está eliminado para componentes de función a partir de React 19. Si lo encuentras, es código anterior y hay que traducirlo a parámetros por defecto. En componentes de clase sigue funcionando, porque una clase no tiene una firma donde escribir los valores por defecto.
- La prop especial
children
childrenHay una prop que no se pasa como atributo: todo lo que escribes entre la etiqueta de apertura y la de cierre de un componente llega en la prop children.
<Panel titulo="Bicicletas disponibles">
<p>Esto de aquí dentro es children</p>
<TarjetaBicicleta bicicleta={bicicletas[0]} />
</Panel>El componente que lo recibe:
// src/componentes/Panel.jsx
function Panel({ titulo, children }) {
return (
<section className="panel">
<h2 className="panel__titulo">{titulo}</h2>
<div className="panel__cuerpo">{children}</div>
</section>
);
}
export default Panel;children es la base del patrón de composición: escribes componentes que aportan una estructura o un comportamiento y dejas que quien los use decida el contenido.
// src/App.jsx (fragmento)
<Panel titulo="Bicicletas del centro">
<TarjetaBicicleta bicicleta={bicicletas[0]} />
<TarjetaBicicleta bicicleta={bicicletas[1]} />
</Panel>
<Panel titulo="Aviso">
<p>El servicio de carga está en mantenimiento hasta el lunes.</p>
</Panel>Un mismo Panel, dos contenidos completamente distintos. Sin children habría que inventar props para cada caso posible, o duplicar el componente.
Cosas que conviene saber sobre children:
- Puede ser cualquier cosa: texto, un elemento, varios elementos, un array, o nada (
undefined). childrenno se puede pasar también como atributo. Si escribes<Panel children="hola">adiós</Panel>, el contenido entre etiquetas gana.- Un componente puede tener varias «zonas» de contenido pasando elementos JSX por props con nombre (
cabecera={<…/>},pie={<…/>}). Es lo que a veces se llama slots.
Este patrón tiene mucho más recorrido —contenedores genéricos, envoltorios, especialización de componentes— y se profundiza en Composición vs Herencia.
- Las props son de solo lectura
Esta es la regla más importante de la lección, y no admite excepciones:
Un componente nunca debe modificar sus propias props. React funciona bajo la premisa de que un componente, ante las mismas props, produce siempre el mismo resultado. A eso se le llama ser una función pura.
// PROHIBIDO
function TarjetaBicicleta({ bicicleta }) {
bicicleta.estado = 'alquilada'; // muta el objeto del padre
return <h3>{bicicleta.modelo}</h3>;
}// PROHIBIDO
function TarjetaBicicleta(props) {
props.modelo = props.modelo.toUpperCase(); // asignación directa sobre props
return <h3>{props.modelo}</h3>;
}Qué pasa exactamente si lo intentas
Depende de qué estés mutando, y ninguno de los dos escenarios es bueno:
| Caso | Qué ocurre |
|---|---|
props.algo = … |
React congela el objeto props en desarrollo: la asignación falla en modo estricto con TypeError: Cannot assign to read only property |
props.objeto.campo = … |
No hay error. Estás mutando el objeto original del padre, que React no ha congelado. Peor todavía: React no se entera del cambio, así que no se dispara ningún render y la pantalla puede quedar desincronizada de los datos |
El segundo caso es el peligroso: silencioso, difícil de depurar y capaz de producir «datos que cambian sin que nadie los pinte». En CicloUrbano, mutar bicicleta.estado dentro de la tarjeta modificaría el array bicicletas importado de dominio.js para toda la aplicación.
Qué hacer en su lugar
- Si solo necesitas un valor transformado para pintarlo, calcula una variable local. Eso es perfectamente correcto: lo prohibido es escribir en la prop, no leerla ni derivar de ella.
function TarjetaBicicleta({ bicicleta }) {
const modeloEnMayusculas = bicicleta.modelo.toUpperCase(); // ✅ variable nueva
const precioFormateado = bicicleta.precioHora.toFixed(2); // ✅
return (
<article className="tarjeta-bicicleta">
<h3>{modeloEnMayusculas}</h3>
<p><strong>{precioFormateado} € / hora</strong></p>
</article>
);
}- Si el dato tiene que cambiar de verdad, no es una prop: es estado, y lo verás en la próxima lección. Y si el cambio lo origina el hijo pero el dato pertenece al padre, el hijo avisa mediante una función recibida por props (04-01).
- Difundir props con el operador de propagación
El operador de propagación (...) permite pasar todas las propiedades de un objeto como props de golpe:
const bici = { id: 'bici-001', modelo: 'Urbana Clásica', tipo: 'urbana', estado: 'disponible', estacionId: 'est-01', precioHora: 2.5 };
// Escribir esto...
<TarjetaBicicleta {...bici} />
// ...equivale exactamente a esto:
<TarjetaBicicleta
id={bici.id}
modelo={bici.modelo}
tipo={bici.tipo}
estado={bici.estado}
estacionId={bici.estacionId}
precioHora={bici.precioHora}
/>Y el componente las recibe como props sueltas:
También se puede combinar con props explícitas, y el orden manda: lo que se escribe después gana.
<TarjetaBicicleta {...bici} estado="mantenimiento" /> {/* estado = 'mantenimiento' */}
<TarjetaBicicleta estado="mantenimiento" {...bici} /> {/* estado = 'disponible', el spread pisa */}Cuándo NO conviene usarlo
Es una herramienta cómoda que se abusa con facilidad. Los inconvenientes:
| Problema | Explicación |
|---|---|
| El contrato desaparece | Leyendo <TarjetaBicicleta {...bici} /> no sabes qué props recibe realmente el componente. Hay que abrir dos ficheros para averiguarlo |
| Props de más | Se cuelan todas las claves del objeto, incluidas las que el componente no usa (id, estacionId). Si algún día se reenvían al DOM, aparecen avisos de atributos desconocidos |
| Acoplamiento oculto | Si mañana dominio.js renombra precioHora a precio, el componente deja de recibirlo y no falla: simplemente pinta undefined |
| Refactorizaciones a ciegas | Buscar «quién pasa la prop estado» no encuentra nada, porque nadie la escribe |
Los usos legítimos son los componentes envoltorio que reenvían props que no conocen:
// Un botón propio que añade estilos y reenvía todo lo demás al <button> real
function BotonMarca({ children, ...resto }) {
return (
<button className="boton-marca" {...resto}>
{children}
</button>
);
}
<BotonMarca type="submit" disabled>Reservar</BotonMarca>Aquí ...resto recoge todas las props que no hemos desestructurado (type, disabled, onClick…) y las reenvía. Ese es el caso en el que el operador brilla.
Regla del curso: en CicloUrbano pasaremos el objeto completo como una prop con nombre —bicicleta={bici}— en lugar de difundirlo. El contrato queda explícito y la firma del componente se lee sola.
- Documentar el contrato de un componente
Las props son el contrato público de tu componente. En JavaScript puro, nada impide pasar un número donde se espera un objeto, así que conviene dejarlo escrito.
Nivel 1: un comentario en la cabecera
Cuesta treinta segundos y resuelve el 80 % de los casos.
/**
* Tarjeta de una bicicleta del catálogo de CicloUrbano.
*
* Props:
* - bicicleta (objeto, obligatorio) { id, modelo, tipo, estado, estacionId, precioHora }
* - destacada (booleano, opcional, por defecto false) resalta la tarjeta
*/
function TarjetaBicicleta({ bicicleta, destacada = false }) { … }Nivel 2: propTypes
Antes de que TypeScript se generalizara, la comprobación de tipos en React se hacía con la librería prop-types, que valida en tiempo de ejecución y solo en desarrollo, imprimiendo un aviso en consola:
import PropTypes from 'prop-types';
TarjetaBicicleta.propTypes = {
bicicleta: PropTypes.shape({
modelo: PropTypes.string.isRequired,
precioHora: PropTypes.number.isRequired
}).isRequired,
destacada: PropTypes.bool
};Lo mencionamos porque lo encontrarás en muchísimo código existente, pero hoy está desaconsejado para proyectos nuevos: prop-types es una dependencia extra, solo avisa cuando el error ya ha ocurrido y no ayuda al editor.
Nivel 3: TypeScript
Es la respuesta moderna: los tipos se comprueban antes de ejecutar, el editor autocompleta las props y renombrar un campo se propaga solo.
El tipado serio de componentes es el contenido de TypeScript con React. Hasta entonces, en CicloUrbano usaremos el nivel 1: un comentario claro encima de cada componente reutilizable.
| Enfoque | Cuándo comprueba | Coste | Recomendación |
|---|---|---|---|
| Comentario | Nunca (lo lee una persona) | Nulo | ✅ Mínimo imprescindible |
propTypes |
En ejecución, solo en desarrollo | Una dependencia | Solo en proyectos que ya lo usan |
| TypeScript | Al escribir y al compilar | Configuración inicial | ✅ Lo ideal en proyectos serios |
- El catálogo de CicloUrbano, ya parametrizado
Es el momento de romper el techo. Vamos a convertir TarjetaBicicleta y EtiquetaEstado en componentes parametrizados y a pintar bicicletas distintas.
EtiquetaEstado con props
// src/componentes/EtiquetaEstado.jsx
/**
* Distintivo visual del estado de una bicicleta.
* Props:
* - estado (cadena, opcional, por defecto 'disponible'):
* 'disponible' | 'alquilada' | 'mantenimiento'
*/
function EtiquetaEstado({ estado = 'disponible' }) {
return <span className={`estado estado--${estado}`}>{estado}</span>;
}
export default EtiquetaEstado;La clase se construye con una plantilla: estado--${estado} produce estado--disponible, estado--alquilada o estado--mantenimiento, que son las clases CSS que definimos en la lección 02-01. Toda la regla «qué color corresponde a qué estado» vive ahora en un solo sitio.
TarjetaBicicleta con props
// src/componentes/TarjetaBicicleta.jsx
import EtiquetaEstado from './EtiquetaEstado.jsx';
/**
* Tarjeta de una bicicleta del catálogo de CicloUrbano.
* Props:
* - bicicleta (objeto, obligatorio) { id, modelo, tipo, estado, estacionId, precioHora }
* - nombreEstacion (cadena, opcional, por defecto 'Estación desconocida')
*/
function TarjetaBicicleta({ bicicleta, nombreEstacion = 'Estación desconocida' }) {
const precioFormateado = bicicleta.precioHora.toFixed(2).replace('.', ',');
return (
<article className={`tarjeta-bicicleta tarjeta-bicicleta--${bicicleta.tipo}`}>
<h3>
{bicicleta.modelo} <EtiquetaEstado estado={bicicleta.estado} />
</h3>
<p>Tipo: {bicicleta.tipo}</p>
<p>Estación: {nombreEstacion}</p>
<p>
<strong>{precioFormateado} € / hora</strong>
</p>
</article>
);
}
export default TarjetaBicicleta;Lo que ha pasado aquí, punto por punto:
{ bicicleta, nombreEstacion = '…' }: destructuring en la firma, con un valor por defecto para la prop opcional.precioFormateado: una variable derivada de la prop. Leemos la prop y calculamos; no la modificamos.toFixed(2)da"2.50"y elreplacelo convierte al formato decimal español,"2,50".tarjeta-bicicleta--${bicicleta.tipo}: la clase de variante ya no está escrita a mano; sale del dato.urbana,electricaocarga.<EtiquetaEstado estado={bicicleta.estado} />:TarjetaBicicletaes padre deEtiquetaEstadoy le pasa una prop. Las props descienden por el árbol de nivel en nivel.
ListaBicicletas reparte los datos
// src/componentes/ListaBicicletas.jsx
import TarjetaBicicleta from './TarjetaBicicleta.jsx';
/**
* Sección del catálogo. Recibe las tres primeras bicicletas por props.
* (En 03-03 pasará a recibir el array completo y recorrerlo con map.)
*/
function ListaBicicletas({ primera, segunda, tercera }) {
return (
<section className="lista-bicicletas">
<h2>Bicicletas disponibles</h2>
<TarjetaBicicleta bicicleta={primera} nombreEstacion="Plaza Mayor" />
<TarjetaBicicleta bicicleta={segunda} nombreEstacion="Plaza Mayor" />
<TarjetaBicicleta bicicleta={tercera} nombreEstacion="Parque Norte" />
</section>
);
}
export default ListaBicicletas;Sí, tres props numeradas es una solución fea, y lo es a propósito: es el síntoma de que falta una herramienta. Esa herramienta es recorrer un array con map, y llega en Listas y Claves. Fíjate en que el problema ya no es de props, sino de repetición.
App conecta los datos reales
// src/App.jsx
import { bicicletas } from './datos/dominio.js';
import Cabecera from './componentes/Cabecera.jsx';
import ListaBicicletas from './componentes/ListaBicicletas.jsx';
import PieDePagina from './componentes/PieDePagina.jsx';
function App() {
return (
<>
<Cabecera />
<main>
<ListaBicicletas
primera={bicicletas[0]}
segunda={bicicletas[1]}
tercera={bicicletas[2]}
/>
</main>
<PieDePagina />
</>
);
}
export default App;Guarda y mira el navegador. Por fin:
| Tarjeta | Modelo | Distintivo | Borde de variante |
|---|---|---|---|
| 1 | Urbana Clásica | verde disponible |
verde (urbana) |
| 2 | Eléctrica Pro | naranja alquilada |
naranja (electrica) |
| 3 | Carga Max | rojo mantenimiento |
rojo (carga) |
Tres tarjetas distintas producidas por un único componente. Ese es el momento en el que React empieza a rentabilizarse: cualquier cambio de diseño en la tarjeta se escribe una vez y se aplica a las tres.
El árbol de props
flowchart TD
DOM["dominio.js<br/>bicicletas[]"] --> APP[App]
APP -- "primera / segunda / tercera" --> LIS[ListaBicicletas]
LIS -- "bicicleta, nombreEstacion" --> T1["TarjetaBicicleta<br/>bici-001"]
LIS -- "bicicleta, nombreEstacion" --> T2["TarjetaBicicleta<br/>bici-002"]
LIS -- "bicicleta, nombreEstacion" --> T3["TarjetaBicicleta<br/>bici-003"]
T1 -- "estado" --> E1[EtiquetaEstado]
T2 -- "estado" --> E2[EtiquetaEstado]
T3 -- "estado" --> E3[EtiquetaEstado]
Todas las flechas apuntan hacia abajo. Ninguna sube. Eso es el flujo unidireccional de React.
Errores Comunes y Consejos
- Pasar números como cadenas.
precioHora="2.5"entrega el texto"2.5", y cualquier operación aritmética otoFixedfallará. Si no es texto literal, va entre llaves. - Olvidar las llaves de la desestructuración.
function TarjetaBicicleta(bicicleta)recibe el objetopropscompleto bajo el nombrebicicleta, así quebicicleta.modeloesundefined. La firma correcta esfunction TarjetaBicicleta({ bicicleta }). - Mutar una prop de tipo objeto.
bicicleta.estado = 'alquilada'no da error y por eso es tan peligroso: modificas el dato del padre sin que React lo sepa. Si el dato debe cambiar, es estado. - Ejecutar la función al pasarla.
alReservar={reservar()}llama areservardurante el render y pasa su resultado. Sin paréntesis:alReservar={reservar}. - Confiar en el valor por defecto ante un
null. Solo se aplica conundefined. Si el padre puede enviarnull, gestiónalo explícitamente. - Abusar de
{...objeto}. Cómodo de escribir, caro de mantener. Resérvalo para envoltorios que reenvían props desconocidas. - Usar
defaultPropsen un componente de función. Eliminado en React 19. Usa parámetros por defecto. - Consejo: la firma es documentación.
function TarjetaBicicleta({ bicicleta, destacada = false })dice más que cualquier comentario. Cuida los nombres de las props: son el vocabulario público del componente. - Consejo: si un componente recibe más de cinco o seis props, sospecha. O hace demasiadas cosas, o varias props deberían viajar agrupadas en un objeto.
- Consejo: inspecciona las props en DevTools. La pestaña Components muestra las props reales de cada instancia. Es la forma más rápida de descubrir que estás pasando
undefined.
Ejercicios
Ejercicio 1
Convierte TarjetaEstacion (creada en la lección 01-03 con datos fijos) en un componente parametrizado. Debe recibir una prop estacion con la forma del dominio.js y una prop opcional bicicletasEnEstacion (número, por defecto 0), y mostrar el nombre, el barrio, el total de plazas y el número de bicicletas. Después úsalo en App.jsx para pintar las tres estaciones de CicloUrbano, escribiéndolas una a una.
Ejercicio 2
Crea el componente Panel descrito en el apartado 6 (src/componentes/Panel.jsx), con props titulo (cadena, obligatoria) y children. Añádele una prop opcional pie que admita un elemento JSX y se pinte debajo del contenido si se proporciona. Luego úsalo dos veces en App.jsx: una envolviendo la lista de bicicletas y otra con un aviso de texto.
Pista: para pintar el pie solo si existe, recuerda que en JSX {undefined} no pinta nada.
Ejercicio 3
Este componente tiene cuatro errores relacionados con las props. Encuéntralos, explica qué provoca cada uno y escribe la versión corregida.
function FilaBicicleta(bicicleta, precioHora = 0) {
bicicleta.modelo = bicicleta.modelo.toUpperCase();
return (
<tr>
<td>{bicicleta.modelo}</td>
<td>{precioHora.toFixed(2)} €</td>
</tr>
);
}
// Uso:
<FilaBicicleta bicicleta={bicicletas[0]} precioHora="2.5" />Soluciones
Solución 1.
// src/componentes/TarjetaEstacion.jsx
/**
* Tarjeta de una estación de CicloUrbano.
* Props:
* - estacion (objeto, obligatorio) { id, nombre, barrio, plazas }
* - bicicletasEnEstacion (número, opcional, por defecto 0)
*/
function TarjetaEstacion({ estacion, bicicletasEnEstacion = 0 }) {
const plazasLibres = estacion.plazas - bicicletasEnEstacion;
return (
<article className="tarjeta-estacion">
<h3>{estacion.nombre}</h3>
<p>Barrio: {estacion.barrio}</p>
<p>Plazas: {estacion.plazas}</p>
<p>
Bicicletas: {bicicletasEnEstacion} · Libres: {plazasLibres}
</p>
</article>
);
}
export default TarjetaEstacion;// src/App.jsx (fragmento)
import { estaciones } from './datos/dominio.js';
import TarjetaEstacion from './componentes/TarjetaEstacion.jsx';
<section>
<h2>Estaciones</h2>
<TarjetaEstacion estacion={estaciones[0]} bicicletasEnEstacion={2} />
<TarjetaEstacion estacion={estaciones[1]} bicicletasEnEstacion={2} />
<TarjetaEstacion estacion={estaciones[2]} bicicletasEnEstacion={1} />
</section>Fíjate en plazasLibres: es un valor derivado de las props, calculado en cada render. No es una prop ni hace falta guardarlo en ningún sitio. Esa distinción entre lo que se guarda y lo que se calcula será central en la próxima lección.
Solución 2.
// src/componentes/Panel.jsx
/**
* Contenedor con título y contenido libre.
* Props:
* - titulo (cadena, obligatorio)
* - children (contenido, obligatorio)
* - pie (elemento JSX, opcional)
*/
function Panel({ titulo, children, pie }) {
return (
<section className="panel">
<h2 className="panel__titulo">{titulo}</h2>
<div className="panel__cuerpo">{children}</div>
{pie && <div className="panel__pie">{pie}</div>}
</section>
);
}
export default Panel;// src/App.jsx (fragmento)
<Panel titulo="Catálogo" pie={<small>Precios con IVA incluido.</small>}>
<ListaBicicletas
primera={bicicletas[0]}
segunda={bicicletas[1]}
tercera={bicicletas[2]}
/>
</Panel>
<Panel titulo="Aviso">
<p>El servicio de bicicletas de carga está en mantenimiento hasta el lunes.</p>
</Panel>El mismo componente envuelve una lista completa en un caso y un párrafo en el otro: eso es lo que aporta children. La expresión {pie && <div…>} es renderizado condicional, que se estudia a fondo en Renderizado Condicional; aquí basta con saber que si pie es undefined, no se pinta nada.
Solución 3.
Los cuatro errores:
- Firma incorrecta.
function FilaBicicleta(bicicleta, precioHora = 0)trata las props como dos parámetros distintos. React siempre pasa un único objeto como primer argumento, así quebicicletaacaba siendo el objetopropsentero yprecioHoranunca recibe nada: se queda en su valor por defecto,0. Hay que desestructurar:({ bicicleta, precioHora = 0 }). - Mutación de una prop.
bicicleta.modelo = bicicleta.modelo.toUpperCase()modifica el objeto deldominio.js, afectando a toda la aplicación y sin disparar ningún render. Hay que calcular una variable nueva. - Número pasado como cadena.
precioHora="2.5"entrega el texto"2.5";"2.5".toFixedno existe y lanzaTypeError. Debe serprecioHora={2.5}. - Contrato incoherente.
precioHoraya viene dentro del objetobicicleta; pasarlo además por separado duplica la fuente de verdad y permite que las dos se contradigan. Debe leerse de la bicicleta.
Versión corregida:
// src/componentes/FilaBicicleta.jsx
/**
* Fila de una bicicleta para vistas en tabla.
* Props:
* - bicicleta (objeto, obligatorio) { id, modelo, tipo, estado, estacionId, precioHora }
*/
function FilaBicicleta({ bicicleta }) {
const modeloEnMayusculas = bicicleta.modelo.toUpperCase();
const precioFormateado = bicicleta.precioHora.toFixed(2).replace('.', ',');
return (
<tr>
<td>{modeloEnMayusculas}</td>
<td>{precioFormateado} €</td>
</tr>
);
}
export default FilaBicicleta;Conclusión
Las props son el mecanismo con el que un componente recibe datos del exterior, y con ellas el catálogo de CicloUrbano ha dejado de ser una maqueta: un solo TarjetaBicicleta pinta ahora tres bicicletas distintas con su modelo, su precio, su variante de color y su distintivo de estado. Sabes pasarlas como atributos —texto entre comillas, todo lo demás entre llaves—, recibirlas con destructuring en la firma, darles valores por defecto con parámetros por defecto (y que defaultProps ya no existe para funciones en React 19), pasar cualquier tipo de valor —incluidos objetos, funciones y elementos JSX— y usar la prop especial children para construir contenedores como Panel que envuelven contenido arbitrario.
Y tienes clara la regla que sostiene todo el modelo: las props son de solo lectura. Un componente las lee y deriva valores de ellas, pero nunca las escribe. El flujo de datos baja por el árbol y no sube, lo que hace que rastrear el origen de cualquier dato sea siempre posible. También sabes que difundir props con {...objeto} es cómodo pero borra el contrato, y que ese contrato conviene documentarlo, hoy con un comentario y en el futuro con TypeScript.
Queda una pregunta abierta, y es grande: si las props vienen de fuera y no se pueden modificar, ¿cómo cambia algo en una aplicación React? Cuando el usuario elige un filtro, reserva una bicicleta o pulsa un botón, algo tiene que cambiar y provocar un nuevo render. Ese algo es el estado: la memoria propia de un componente, el dato que sí puede cambiar y que, al hacerlo, dispara el ciclo de renderizado que estudiaste en 01-05. Es el tema de la siguiente lección: State: Gestión del Estado del Componente.
Curso de React
Módulo 1: Introducción a React
- ¿Qué es React?
- Configuración del Entorno de Desarrollo
- Hola Mundo en React
- JSX: Extensión de Sintaxis de JavaScript
- Cómo Renderiza React: Virtual DOM y Reconciliación
Módulo 2: Componentes de React
- Entendiendo los Componentes
- Componentes Funcionales vs de Clase
- Props: Pasando Datos a Componentes
- State: Gestión del Estado del Componente
- Estilos en los Componentes: CSS, Módulos y Utilidades
Módulo 3: Trabajando con Eventos
- Manejo de Eventos en React
- Renderizado Condicional
- Listas y Claves
- Formularios y Componentes Controlados
- Validación de Formularios y Componentes No Controlados
- Accesibilidad en Componentes Interactivos
Módulo 4: Conceptos Avanzados de Componentes
- Elevando el Estado
- Composición vs Herencia
- Métodos del Ciclo de Vida de React
- Hooks: Introducción y Uso Básico
- Límites de Error: Capturar Fallos en la Interfaz
Módulo 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef y Acceso al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalizados
Módulo 6: Enrutamiento en React
- Introducción a React Router
- Configuración de React Router
- Rutas Anidadas
- Navegación Programática
- Rutas Protegidas y Control de Acceso
Módulo 7: Gestión del Estado
- Introducción a la Gestión del Estado
- API de Contexto
- Redux: Introducción y Configuración
- Redux: Acciones y Reductores
- Redux: Conectando a React
- Estado del Servidor: Peticiones, Caché y Sincronización
Módulo 8: Optimización del Rendimiento
- Técnicas de Optimización del Rendimiento en React
- Memorización con React.memo
- Hooks useMemo y useCallback
- División de Código y Carga Perezosa
- Medir el Rendimiento con React DevTools Profiler
Módulo 9: Pruebas en React
- Introducción a las Pruebas
- Pruebas Unitarias con Jest
- Pruebas de Componentes con React Testing Library
- Pruebas de Código Asíncrono y Simulación de APIs
- Pruebas de Extremo a Extremo con Cypress
Módulo 10: Temas Avanzados
- Renderizado del Lado del Servidor (SSR) con Next.js
- Generación de Sitios Estáticos (SSG) con Next.js
- Suspense y React Server Components
- TypeScript con React
- React Native: Creación de Aplicaciones Móviles
