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

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

Tutorial: Aplicación React Native para Agregar Tareas - Minimalista

5. Vista Rapida: Estructura Projecto Base