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

  1. Qué son las props y qué problema resuelven
  2. Pasar props y recibirlas
  3. Destructuring en la firma del componente
  4. Qué tipos de valores se pueden pasar
  5. Valores por defecto
  6. La prop especial children
  7. Las props son de solo lectura
  8. Difundir props con el operador de propagación
  9. Documentar el contrato de un componente
  10. El catálogo de CicloUrbano, ya parametrizado

  1. 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.

  1. Pasar props y recibirlas

Pasarlas: atributos en el JSX

<TarjetaBicicleta modelo="Urbana Clásica" precioHora={2.5} />

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. props es la convención universal, pero es un parámetro normal. Llámalo props.
  • 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.

  1. 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.

  1. 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:

<Panel titulo="Flota" accion={<button>Añadir bicicleta</button>}>
  …
</Panel>

  1. 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.

  1. La prop especial children

Hay 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).
  • children no 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.

  1. 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).

  1. 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:

function TarjetaBicicleta({ modelo, tipo, estado, precioHora }) { … }

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.

  1. 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.

type PropsTarjetaBicicleta = {
  bicicleta: Bicicleta;
  destacada?: boolean;
};

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

  1. 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 el replace lo 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, electrica o carga.
  • <EtiquetaEstado estado={bicicleta.estado} />: TarjetaBicicleta es padre de EtiquetaEstado y 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 o toFixed fallará. Si no es texto literal, va entre llaves.
  • Olvidar las llaves de la desestructuración. function TarjetaBicicleta(bicicleta) recibe el objeto props completo bajo el nombre bicicleta, así que bicicleta.modelo es undefined. La firma correcta es function 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 a reservar durante el render y pasa su resultado. Sin paréntesis: alReservar={reservar}.
  • Confiar en el valor por defecto ante un null. Solo se aplica con undefined. Si el padre puede enviar null, gestiónalo explícitamente.
  • Abusar de {...objeto}. Cómodo de escribir, caro de mantener. Resérvalo para envoltorios que reenvían props desconocidas.
  • Usar defaultProps en 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:

  1. 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í que bicicleta acaba siendo el objeto props entero y precioHora nunca recibe nada: se queda en su valor por defecto, 0. Hay que desestructurar: ({ bicicleta, precioHora = 0 }).
  2. Mutación de una prop. bicicleta.modelo = bicicleta.modelo.toUpperCase() modifica el objeto del dominio.js, afectando a toda la aplicación y sin disparar ningún render. Hay que calcular una variable nueva.
  3. Número pasado como cadena. precioHora="2.5" entrega el texto "2.5"; "2.5".toFixed no existe y lanza TypeError. Debe ser precioHora={2.5}.
  4. Contrato incoherente. precioHora ya viene dentro del objeto bicicleta; 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;
// Uso correcto:
<FilaBicicleta bicicleta={bicicletas[0]} />

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

Módulo 2: Componentes de React

Módulo 3: Trabajando con Eventos

Módulo 4: Conceptos Avanzados de Componentes

Módulo 5: Hooks de React

Módulo 6: Enrutamiento en React

Módulo 7: Gestión del Estado

Módulo 8: Optimización del Rendimiento

Módulo 9: Pruebas en React

Módulo 10: Temas Avanzados

Módulo 11: Proyecto: Construyendo una Aplicación Completa

© Copyright 2026. Todos los derechos reservados