21. Props

 

Tutorial: Props (Propiedades) en React Native

En este tutorial aprenderás a usar Props (propiedades) en React Native, un concepto fundamental para pasar datos entre componentes y hacerlos dinámicos y reutilizables.


🎯 Objetivo del Tutorial

Comprender cómo definir y usar Props para crear componentes dinámicos y reutilizables en React Native.


📚 ¿Qué son las Props?

Props (abreviatura de "properties") son datos que se pasan de un componente padre a un componente hijo. Son inmutables (no se pueden modificar en el componente hijo) y permiten la comunicación entre componentes.


🔧 Parte 1: Props en Functional Components

1.1 Crear una interfaz para las Props

tsx

// src/components/MiPrimerComponente/MiPrimerComponente.tsx

import React from 'react';

import {ColorValue, StyleSheet, View, ViewStyle} from 'react-native';


// Definir interfaz para las Props

interface MiPrimerComponenteProps {

  color: ColorValue; // Tipo específico para colores en React Native

}


function MiPrimerComponente(props: MiPrimerComponenteProps): JSX.Element {

  // Desestructurar props para acceder fácilmente a sus valores

  const {color} = props;

  

  return (

    <View

      style={[

        styles.mainComponent,

        {

          backgroundColor: color, // Usar el color de las props

        },

      ]}

    />

  );

}


const styles = StyleSheet.create({

  mainComponent: {

    width: 50,

    height: 50,

    borderRadius: 25,

    borderColor: 'green',

    borderWidth: 5,

  } as ViewStyle,

});


export default MiPrimerComponente;

1.2 Desestructuración directa (forma más común)

tsx

// Desestructurar directamente en los parámetros

function MiPrimerComponente({color}: MiPrimerComponenteProps): JSX.Element {

  return (

    <View

      style={[

        styles.mainComponent,

        {backgroundColor: color}, // Estilo inline dinámico

      ]}

    />

  );

}

1.3 Props con valores por defecto

tsx

interface MiPrimerComponenteProps {

  color?: ColorValue; // Prop opcional con ?

  tamaño?: number;

}


// Valores por defecto usando desestructuración

function MiPrimerComponente({

  color = 'red',      // Valor por defecto

  tamaño = 50,        // Valor por defecto

}: MiPrimerComponenteProps): JSX.Element {

  return (

    <View

      style={[

        styles.mainComponent,

        {

          backgroundColor: color,

          width: tamaño,

          height: tamaño,

          borderRadius: tamaño / 2,

        },

      ]}

    />

  );

}


🏗️ Parte 2: Props en Class Components

2.1 Definir Props en una clase

tsx

// src/components/MiPrimerComponenteClass/MiPrimerComponenteClass.tsx

import React from 'react';

import {ColorValue, StyleSheet, View, ViewStyle} from 'react-native';


// Definir interfaz para las Props

interface MiPrimerComponenteClassProps {

  color: ColorValue;

}


// Especificar tipos de Props y State en los genéricos

// React.Component<Props, State, Snapshot>

class MiPrimerComponenteClass extends React.Component<MiPrimerComponenteClassProps> {

  render(): React.ReactNode {

    // Desestructurar props usando this.props

    const {color} = this.props;

    

    return (

      <View

        style={[

          styles.mainComponent,

          {

            backgroundColor: color, // Usar color de las props

          },

        ]}

      />

    );

  }

}


const styles = StyleSheet.create({

  mainComponent: {

    width: 50,

    height: 50,

    borderRadius: 25,

    borderColor: 'green',

    borderWidth: 5,

  } as ViewStyle,

});


export default MiPrimerComponenteClass;

2.2 Class Component con Props y State

tsx

interface ComponenteProps {

  color: ColorValue;

  tamañoInicial?: number;

}


interface ComponenteState {

  tamañoActual: number;

}


class MiComponente extends React.Component<ComponenteProps, ComponenteState> {

  constructor(props: ComponenteProps) {

    super(props);

    this.state = {

      tamañoActual: props.tamañoInicial || 50,

    };

  }

  

  render() {

    const {color} = this.props;

    const {tamañoActual} = this.state;

    

    return (

      <View

        style={{

          backgroundColor: color,

          width: tamañoActual,

          height: tamañoActual,

        }}

      />

    );

  }

}


📱 Parte 3: Usar Componentes con Props

3.1 App.tsx usando Props

tsx

// App.tsx

import React from 'react';

import {SafeAreaView} from 'react-native';

import MiPrimerComponente from './src/components/MiPrimerComponente/MiPrimerComponente';

import MiPrimerComponenteClass from './src/components/MiPrimerComponenteClass/MiPrimerComponenteClass';


function App(): JSX.Element {

  return (

    <SafeAreaView style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>

      {/* Pasar props como atributos HTML */}

      <MiPrimerComponente color="blue" />

      

      <MiPrimerComponenteClass color="pink" />

      

      {/* Reutilizar con diferentes props */}

      <MiPrimerComponente color="red" />

      <MiPrimerComponente color="green" />

      <MiPrimerComponente color="orange" />

      <MiPrimerComponente color="purple" />

    </SafeAreaView>

  );

}


export default App;

3.2 Sintaxis para pasar Props

tsx

// Strings (comillas dobles o simples)

<MiComponente color="red" texto="Hola" />


// Números (sin comillas)

<MiComponente tamaño={100} cantidad={5} />


// Booleanos (sin comillas)

<MiComponente activo={true} visible={false} />


// Arrays

<MiComponente items={[1, 2, 3]} />


// Objetos

<MiComponente config={{tamaño: 100, color: 'red'}} />


// Funciones

<MiComponente onPress={() => console.log('Presionado')} />


// Variables

const colorFavorito = 'blue';

<MiComponente color={colorFavorito} />


🎨 Parte 4: Props Avanzadas

4.1 Children Prop (contenido anidado)

tsx

interface TarjetaProps {

  titulo: string;

  children: React.ReactNode; // Para contenido anidado

}


function Tarjeta({titulo, children}: TarjetaProps): JSX.Element {

  return (

    <View style={styles.tarjeta}>

      <Text style={styles.titulo}>{titulo}</Text>

      {/* Renderizar contenido anidado */}

      {children}

    </View>

  );

}


// Uso con children

<Tarjeta titulo="Mi Tarjeta">

  <Text>Contenido de la tarjeta</Text>

  <Button title="Presionar" />

</Tarjeta>

4.2 Props con tipos complejos

tsx

import {ImageSourcePropType} from 'react-native';


interface PerfilProps {

  // Tipos específicos de React Native

  imagen: ImageSourcePropType;

  color: ColorValue;

  estilo?: ViewStyle;

  

  // Tipos TypeScript estándar

  nombre: string;

  edad: number;

  activo: boolean;

  habilidades: string[];

  config: {

    mostrarEmail: boolean;

    tema: 'claro' | 'oscuro';

  };

  

  // Funciones con tipado específico

  onPress: () => void;

  onChange: (nuevoValor: string) => void;

}


function Perfil(props: PerfilProps): JSX.Element {

  // Implementación...

}

4.3 Props de estilo dinámico

tsx

interface BotonProps {

  texto: string;

  tipo?: 'primario' | 'secundario' | 'peligro';

  deshabilitado?: boolean;

}


function Boton({texto, tipo = 'primario', deshabilitado = false}: BotonProps): JSX.Element {

  // Determinar estilo basado en props

  const colorFondo = {

    primario: '#007AFF',

    secundario: '#5856D6',

    peligro: '#FF3B30',

  }[tipo];

  

  return (

    <View

      style={[

        styles.boton,

        {

          backgroundColor: colorFondo,

          opacity: deshabilitado ? 0.5 : 1,

        },

      ]}

    >

      <Text style={styles.texto}>{texto}</Text>

    </View>

  );

}


🔄 Parte 5: Combinar Múltiples Estilos con Props

5.1 Estilos condicionales con Props

tsx

interface CajaProps {

  color?: ColorValue;

  tamaño?: number;

  redondeada?: boolean;

  conBorde?: boolean;

  conSombra?: boolean;

}


function Caja({

  color = 'white',

  tamaño = 100,

  redondeada = false,

  conBorde = false,

  conSombra = false,

}: CajaProps): JSX.Element {

  return (

    <View

      style={[

        // Estilo base

        {width: tamaño, height: tamaño, backgroundColor: color},

        

        // Estilos condicionales

        redondeada && {borderRadius: tamaño / 4},

        conBorde && {borderWidth: 2, borderColor: '#000'},

        conSombra && {

          shadowColor: '#000',

          shadowOffset: {width: 0, height: 2},

          shadowOpacity: 0.25,

          shadowRadius: 3.84,

          elevation: 5,

        },

      ]}

    />

  );

}


// Uso con diferentes combinaciones

<Caja color="blue" tamaño={80} redondeada conSombra />

<Caja color="red" tamaño={120} conBorde />

<Caja color="green" tamaño={60} redondeada conBorde conSombra />

5.2 Props para estilos completos

tsx

interface ComponenteFlexibleProps {

  estiloContenedor?: ViewStyle;

  estiloTexto?: TextStyle;

  contenido: string;

}


function ComponenteFlexible({

  estiloContenedor,

  estiloTexto,

  contenido,

}: ComponenteFlexibleProps): JSX.Element {

  return (

    <View style={[styles.contenedorBase, estiloContenedor]}>

      <Text style={[styles.textoBase, estiloTexto]}>{contenido}</Text>

    </View>

  );

}


// Pasar estilos completos como props

<ComponenteFlexible

  contenido="Hola Mundo"

  estiloContenedor={{padding: 30, backgroundColor: '#f0f0f0'}}

  estiloTexto={{fontSize: 24, color: 'red', fontWeight: 'bold'}}

/>


🧪 Parte 6: Ejercicio Práctico - Componente Botón Reutilizable

6.1 Crear un botón con múltiples props

tsx

// src/components/Boton/Boton.tsx

import React from 'react';

import {

  TouchableOpacity,

  Text,

  StyleSheet,

  ViewStyle,

  TextStyle,

  ColorValue,

} from 'react-native';


interface BotonProps {

  // Props básicas

  titulo: string;

  onPress: () => void;

  

  // Props de estilo

  colorFondo?: ColorValue;

  colorTexto?: ColorValue;

  estiloPersonalizado?: ViewStyle;

  textoEstilo?: TextStyle;

  

  // Props de comportamiento

  deshabilitado?: boolean;

  loading?: boolean;

  

  // Props de variante

  tipo?: 'primario' | 'secundario' | 'outline' | 'text';

  tamaño?: 'pequeño' | 'medio' | 'grande';

}


function Boton({

  titulo,

  onPress,

  colorFondo,

  colorTexto = 'white',

  estiloPersonalizado,

  textoEstilo,

  deshabilitado = false,

  loading = false,

  tipo = 'primario',

  tamaño = 'medio',

}: BotonProps): JSX.Element {

  // Determinar colores basados en el tipo

  const colorPorTipo = {

    primario: '#007AFF',

    secundario: '#5856D6',

    outline: 'transparent',

    text: 'transparent',

  }[tipo];

  

  const fondoFinal = colorFondo || colorPorTipo;

  

  // Determinar tamaño

  const paddingPorTamaño = {

    pequeño: {paddingVertical: 8, paddingHorizontal: 16},

    medio: {paddingVertical: 12, paddingHorizontal: 24},

    grande: {paddingVertical: 16, paddingHorizontal: 32},

  }[tamaño];

  

  // Determinar si tiene borde

  const tieneBorde = tipo === 'outline';

  

  return (

    <TouchableOpacity

      style={[

        styles.boton,

        paddingPorTamaño,

        {

          backgroundColor: fondoFinal,

          borderWidth: tieneBorde ? 2 : 0,

          borderColor: tipo === 'outline' ? colorPorTipo : undefined,

          opacity: deshabilitado || loading ? 0.6 : 1,

        },

        estiloPersonalizado,

      ]}

      onPress={onPress}

      disabled={deshabilitado || loading}

      activeOpacity={0.8}

    >

      <Text

        style={[

          styles.texto,

          {

            color: tipo === 'outline' || tipo === 'text' 

              ? colorPorTipo 

              : colorTexto,

          },

          textoEstilo,

        ]}

      >

        {loading ? 'Cargando...' : titulo}

      </Text>

    </TouchableOpacity>

  );

}


const styles = StyleSheet.create({

  boton: {

    borderRadius: 8,

    alignItems: 'center',

    justifyContent: 'center',

  },

  texto: {

    fontSize: 16,

    fontWeight: '600',

  },

});


export default Boton;

6.2 Usar el botón con diferentes props

tsx

// En App.tsx

import Boton from './src/components/Boton/Boton';


function App(): JSX.Element {

  const manejarPresion = () => {

    console.log('Botón presionado!');

  };

  

  return (

    <SafeAreaView style={{padding: 20}}>

      {/* Botón primario */}

      <Boton 

        titulo="Guardar" 

        onPress={manejarPresion}

        tipo="primario"

      />

      

      {/* Botón secundario */}

      <Boton 

        titulo="Cancelar" 

        onPress={manejarPresion}

        tipo="secundario"

        colorTexto="white"

      />

      

      {/* Botón outline */}

      <Boton 

        titulo="Editar" 

        onPress={manejarPresion}

        tipo="outline"

        colorFondo="transparent"

      />

      

      {/* Botón deshabilitado */}

      <Boton 

        titulo="Deshabilitado" 

        onPress={manejarPresion}

        deshabilitado

      />

      

      {/* Botón con estilo personalizado */}

      <Boton 

        titulo="Personalizado" 

        onPress={manejarPresion}

        estiloPersonalizado={{

          marginTop: 20,

          borderRadius: 20,

          backgroundColor: 'purple',

        }}

        textoEstilo={{

          fontSize: 18,

          fontStyle: 'italic',

        }}

      />

    </SafeAreaView>

  );

}


⚠️ Parte 7: Errores Comunes y Soluciones

7.1 Error: "Property does not exist on type"

tsx

// ❌ INCORRECTO

<MiComponente colorr="red" /> // Typo en el nombre de la prop


// ✅ CORRECTO

<MiComponente color="red" />

7.2 Error: TypeScript - tipo incorrecto

tsx

// ❌ INCORRECTO - string donde se espera number

<MiComponente tamaño="100" />


// ✅ CORRECTO - number sin comillas

<MiComponente tamaño={100} />

7.3 Error: Props requeridas faltantes

tsx

interface Props {

  titulo: string;      // Requerida (sin ?)

  subtitulo?: string;  // Opcional (con ?)

}


// ❌ INCORRECTO - falta prop requerida

<MiComponente />


// ✅ CORRECTO - incluye prop requerida

<MiComponente titulo="Hola" />

7.4 Solución: Prop spreading (esparcir props)

tsx

interface InputProps {

  placeholder: string;

  value: string;

  onChangeText: (text: string) => void;

  [key: string]: any; // Permite props adicionales

}


function Input(props: InputProps): JSX.Element {

  return (

    <TextInput

      {...props} // Esparcir todas las props al TextInput

      style={styles.input}

    />

  );

}


// Uso con props adicionales

<Input

  placeholder="Escribe algo"

  value={texto}

  onChangeText={setTexto}

  keyboardType="email-address" // Prop adicional

  autoCapitalize="none"        // Prop adicional

/>


✅ Parte 8: Buenas Prácticas con Props

8.1 Nomenclatura consistente

tsx

// Usar camelCase para props

<Componente

  colorFondo="blue"    // ✅ camelCase

  tamañoTexto={16}     // ✅ camelCase

  onPressHandler={() => {}} // ✅ prefijo 'on' para eventos

/>

8.2 Documentación con JSDoc

tsx

interface BotonProps {

  /**

   * Texto que se muestra en el botón

   * @default 'Presionar'

   */

  titulo?: string;

  

  /**

   * Color de fondo del botón

   * @default '#007AFF'

   */

  color?: ColorValue;

  

  /**

   * Callback que se ejecuta al presionar el botón

   */

  onPress: () => void;

}

8.3 Props para temas y configuraciones globales

tsx

// src/context/ThemeContext.tsx

import React, {createContext, useContext} from 'react';


interface Theme {

  colores: {

    primario: string;

    secundario: string;

    fondo: string;

    texto: string;

  };

  espaciado: {

    pequeño: number;

    medio: number;

    grande: number;

  };

}


const ThemeContext = createContext<Theme>(defaultTheme);


// Componente que usa el tema

function BotonConTema(): JSX.Element {

  const tema = useContext(ThemeContext);

  

  return (

    <TouchableOpacity

      style={{backgroundColor: tema.colores.primario}}

    >

      <Text style={{color: tema.colores.texto}}>Botón</Text>

    </TouchableOpacity>

  );

}


📊 Parte 9: Props vs Children - ¿Cuándo usar cada uno?

Usar Props cuando:

✅ Datos simples: strings, numbers, booleans
✅ Configuración: opciones, banderas, modos
✅ Eventos: callbacks, handlers
✅ Estilos: colores, tamaños, variantes

Usar Children cuando:

✅ Contenido complejo: múltiples elementos
✅ Layouts: contenedores genéricos
✅ Composición: anidamiento de componentes
✅ Renderizado condicional: contenido dinámico

tsx

// Ejemplo: Tarjeta que acepta children

function Tarjeta({titulo, children}: {titulo: string; children: React.ReactNode}) {

  return (

    <View style={styles.tarjeta}>

      <Text style={styles.titulo}>{titulo}</Text>

      {children}

    </View>

  );

}


// Uso con children

<Tarjeta titulo="Perfil">

  <Text>Nombre: Juan</Text>

  <Text>Edad: 25</Text>

  <Boton titulo="Editar" />

</Tarjeta>


🎉 ¡Felicidades!

Has aprendido a usar Props en React Native:

✅ Definir Props con interfaces TypeScript
✅ Usar Props en Functional y Class Components
✅ Pasar diferentes tipos de datos como props
✅ Crear componentes reutilizables con props dinámicas
✅ Combinar estilos usando props
✅ Evitar errores comunes con props
✅ Seguir buenas prácticas de diseño


📚 Recursos Adicionales


¡Excelente trabajo! Ahora puedes crear componentes altamente reutilizables y configurables. En el próximo tutorial aprenderás sobre State (estado) para crear componentes interactivos


Comentarios

Entradas más populares de este blog

17. Estructura de una aplicación de React Native, una vista mas profunda

Guía Paso a Paso para Entender React Native (antes del Tutorial)

12. Configuracion del entorno de desarrollo en Windows