22. Eventos

 

Curso: Eventos en React Native - Manejo de Interacción del Usuario

📚 Módulo 1: Fundamentos de Eventos en React Native

🎯 Objetivo del Curso

Aprender a manejar eventos en React Native para crear aplicaciones interactivas que respondan a las acciones del usuario.

📋 ¿Qué son los Eventos?

Los eventos son acciones que ocurren en respuesta a la interacción del usuario con la aplicación:

  • Toques/Clics: onPress, onLongPress

  • Texto: onChangeText, onSubmitEditing

  • Scroll: onScroll, onMomentumScrollEnd

  • Gestos: onSwipe, onPinch

  • Teclado: onKeyboardShow, onKeyboardHide


🛠️ Módulo 2: Evento Básico - onPress

2.1 Componentes Táctiles en React Native

React Native proporciona varios componentes para manejar toques:

Componente

Descripción

Uso común

TouchableOpacity

Opacidad al tocar

Botones, elementos interactivos

TouchableHighlight

Cambio de color de fondo

List items, cards

TouchableWithoutFeedback

Sin feedback visual

Áreas táctiles invisibles

Pressable

Componente moderno (React Native 0.63+)

Reemplazo recomendado

2.2 Implementando onPress en Functional Components

tsx

// src/components/BotonConEvento/BotonConEvento.tsx

import React from 'react';

import {

  TouchableOpacity,

  Text,

  StyleSheet,

  ViewStyle,

  ColorValue,

} from 'react-native';


// Definir interfaz para props incluyendo el evento

interface BotonConEventoProps {

  titulo: string;

  colorFondo?: ColorValue;

  colorTexto?: ColorValue;

  estiloPersonalizado?: ViewStyle;

  onPress: () => void; // ✅ Propiedad de evento requerida

}


function BotonConEvento({

  titulo,

  colorFondo = '#007AFF',

  colorTexto = 'white',

  estiloPersonalizado,

  onPress, // ✅ Recibir el manejador de eventos

}: BotonConEventoProps): JSX.Element {

  return (

    <TouchableOpacity

      style={[

        styles.boton,

        {backgroundColor: colorFondo},

        estiloPersonalizado,

      ]}

      onPress={onPress} // ✅ Asignar el manejador de eventos

      activeOpacity={0.7} // Opacidad al presionar (0 a 1)

    >

      <Text style={[styles.texto, {color: colorTexto}]}>

        {titulo}

      </Text>

    </TouchableOpacity>

  );

}


const styles = StyleSheet.create({

  boton: {

    paddingVertical: 12,

    paddingHorizontal: 24,

    borderRadius: 8,

    alignItems: 'center',

    justifyContent: 'center',

    margin: 10,

  },

  texto: {

    fontSize: 16,

    fontWeight: '600',

  },

});


export default BotonConEvento;

2.3 Uso del Componente con Evento

tsx

// App.tsx

import React from 'react';

import {SafeAreaView, Text, View} from 'react-native';

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


function App(): JSX.Element {

  // Manejador de evento como función inline

  const manejarPresionSimple = () => {

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

    alert('Evento onPress ejecutado');

  };


  // Manejador de evento con parámetros

  const manejarPresionConParametro = (mensaje: string) => {

    console.log(`Mensaje: ${mensaje}`);

  };


  // Manejador de evento complejo

  const manejarPresionComplejo = () => {

    console.log('1. Validando datos...');

    console.log('2. Enviando a servidor...');

    console.log('3. Actualizando estado...');

    alert('Proceso completado');

  };


  return (

    <SafeAreaView style={{flex: 1, padding: 20}}>

      <Text style={{fontSize: 24, marginBottom: 20}}>

        Ejemplos de Eventos onPress

      </Text>


      {/* Evento simple */}

      <BotonConEvento

        titulo="Presióname"

        onPress={manejarPresionSimple}

      />


      {/* Evento con parámetro usando arrow function */}

      <BotonConEvento

        titulo="Saludar"

        onPress={() => manejarPresionConParametro('¡Hola Mundo!')}

        colorFondo="#34C759"

      />


      {/* Evento complejo */}

      <BotonConEvento

        titulo="Proceso Completo"

        onPress={manejarPresionComplejo}

        colorFondo="#FF9500"

      />


      {/* Evento inline sin función separada */}

      <BotonConEvento

        titulo="Evento Inline"

        onPress={() => {

          console.log('Evento ejecutado directamente');

          console.log('Hora:', new Date().toLocaleTimeString());

        }}

        colorFondo="#5856D6"

      />

    </SafeAreaView>

  );

}


export default App;


🏗️ Módulo 3: Eventos en Class Components

3.1 Implementando Eventos en Class Components

tsx

// src/components/BotonClase/BotonClase.tsx

import React from 'react';

import {

  TouchableOpacity,

  Text,

  StyleSheet,

  ViewStyle,

  ColorValue,

} from 'react-native';


interface BotonClaseProps {

  titulo: string;

  colorFondo?: ColorValue;

  onPress: () => void;

}


interface BotonClaseState {

  contadorPresiones: number;

  estaPresionado: boolean;

}


class BotonClase extends React.Component<BotonClaseProps, BotonClaseState> {

  constructor(props: BotonClaseProps) {

    super(props);

    this.state = {

      contadorPresiones: 0,

      estaPresionado: false,

    };

  }


  // Manejador de evento como método de clase

  manejarPresion = () => {

    const {onPress} = this.props;

    

    // Actualizar estado

    this.setState(prevState => ({

      contadorPresiones: prevState.contadorPresiones + 1,

      estaPresionado: true,

    }));

    

    // Ejecutar callback del padre

    onPress();

    

    // Resetear estado después de 200ms

    setTimeout(() => {

      this.setState({estaPresionado: false});

    }, 200);

  };


  render() {

    const {titulo, colorFondo = '#FF3B30'} = this.props;

    const {contadorPresiones, estaPresionado} = this.state;

    

    return (

      <TouchableOpacity

        style={[

          styles.boton,

          {backgroundColor: colorFondo},

          estaPresionado && styles.botonPresionado,

        ]}

        onPress={this.manejarPresion} // ✅ Usar método de clase

        activeOpacity={0.7}

      >

        <Text style={styles.texto}>

          {titulo} ({contadorPresiones})

        </Text>

      </TouchableOpacity>

    );

  }

}


const styles = StyleSheet.create({

  boton: {

    paddingVertical: 12,

    paddingHorizontal: 24,

    borderRadius: 8,

    alignItems: 'center',

    justifyContent: 'center',

    margin: 10,

  },

  botonPresionado: {

    transform: [{scale: 0.95}],

  },

  texto: {

    fontSize: 16,

    fontWeight: '600',

    color: 'white',

  },

});


export default BotonClase;

3.2 Usando Ambos Tipos de Componentes

tsx

// App.tsx - Ejemplo completo

import React, {useState} from 'react';

import {SafeAreaView, Text, View, ScrollView} from 'react-native';

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

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


function App(): JSX.Element {

  const [contadorFuncional, setContadorFuncional] = useState(0);

  const [contadorClase, setContadorClase] = useState(0);

  const [mensaje, setMensaje] = useState('Presiona un botón');


  const manejarEventoFuncional = () => {

    const nuevoContador = contadorFuncional + 1;

    setContadorFuncional(nuevoContador);

    setMensaje(`Functional Component: ${nuevoContador} clics`);

    console.log('Evento desde Functional Component');

  };


  const manejarEventoClase = () => {

    const nuevoContador = contadorClase + 1;

    setContadorClase(nuevoContador);

    setMensaje(`Class Component: ${nuevoContador} clics`);

    console.log('Evento desde Class Component');

  };


  return (

    <SafeAreaView style={{flex: 1}}>

      <ScrollView contentContainerStyle={{padding: 20}}>

        <Text style={styles.titulo}>Manejo de Eventos en React Native</Text>

        

        <View style={styles.seccion}>

          <Text style={styles.subtitulo}>Functional Component</Text>

          <BotonConEvento

            titulo="Presióname (Functional)"

            onPress={manejarEventoFuncional}

            colorFondo="#007AFF"

          />

          <Text style={styles.contador}>

            Clics: {contadorFuncional}

          </Text>

        </View>


        <View style={styles.seccion}>

          <Text style={styles.subtitulo}>Class Component</Text>

          <BotonClase

            titulo="Presióname (Class)"

            onPress={manejarEventoClase}

            colorFondo="#FF9500"

          />

          <Text style={styles.contador}>

            Clics: {contadorClase}

          </Text>

        </View>


        <View style={styles.mensajeContainer}>

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

        </View>


        {/* Ejemplos adicionales */}

        <View style={styles.seccion}>

          <Text style={styles.subtitulo}>Más Ejemplos</Text>

          

          <BotonConEvento

            titulo="Alerta Simple"

            onPress={() => alert('¡Hola desde React Native!')}

            colorFondo="#34C759"

          />

          

          <BotonConEvento

            titulo="Cambiar Tema"

            onPress={() => {

              console.log('Cambiando tema...');

              // Lógica para cambiar tema

            }}

            colorFondo="#5856D6"

          />

          

          <BotonConEvento

            titulo="Navegar"

            onPress={() => {

              console.log('Navegando a otra pantalla...');

              // Lógica de navegación

            }}

            colorFondo="#AF52DE"

          />

        </View>

      </ScrollView>

    </SafeAreaView>

  );

}


const styles = {

  titulo: {

    fontSize: 28,

    fontWeight: 'bold',

    textAlign: 'center',

    marginBottom: 30,

    color: '#1D1D1F',

  },

  subtitulo: {

    fontSize: 18,

    fontWeight: '600',

    marginBottom: 10,

    color: '#424245',

  },

  seccion: {

    marginBottom: 30,

    padding: 20,

    backgroundColor: '#F5F5F7',

    borderRadius: 12,

  },

  contador: {

    fontSize: 16,

    textAlign: 'center',

    marginTop: 10,

    color: '#6E6E73',

  },

  mensajeContainer: {

    padding: 20,

    backgroundColor: '#E8F4FF',

    borderRadius: 12,

    marginBottom: 30,

  },

  mensaje: {

    fontSize: 16,

    textAlign: 'center',

    color: '#007AFF',

  },

};


export default App;


🎨 Módulo 4: Tipos de Eventos y Componentes

4.1 Eventos Disponibles en Diferentes Componentes

tsx

// src/components/ComponenteConMultiplesEventos/ComponenteConMultiplesEventos.tsx

import React, {useState} from 'react';

import {

  View,

  Text,

  TextInput,

  TouchableOpacity,

  ScrollView,

  Switch,

  StyleSheet,

} from 'react-native';


function ComponenteConMultiplesEventos(): JSX.Element {

  const [texto, setTexto] = useState('');

  const [esActivo, setEsActivo] = useState(false);

  const [posicionScroll, setPosicionScroll] = useState(0);


  return (

    <View style={styles.container}>

      {/* 1. Evento onPress (TouchableOpacity) */}

      <TouchableOpacity

        style={styles.boton}

        onPress={() => console.log('onPress ejecutado')}

        onLongPress={() => console.log('onLongPress ejecutado (presión larga)')}

        onPressIn={() => console.log('onPressIn: touch comenzó')}

        onPressOut={() => console.log('onPressOut: touch terminó')}

      >

        <Text style={styles.textoBoton}>Presióname</Text>

      </TouchableOpacity>


      {/* 2. Eventos de TextInput */}

      <TextInput

        style={styles.input}

        placeholder="Escribe algo..."

        value={texto}

        onChangeText={nuevoTexto => {

          console.log(`Texto cambiado: ${nuevoTexto}`);

          setTexto(nuevoTexto);

        }}

        onSubmitEditing={() => console.log('onSubmitEditing: Enter presionado')}

        onFocus={() => console.log('onFocus: input enfocado')}

        onBlur={() => console.log('onBlur: input perdió foco')}

        onKeyPress={event => {

          console.log(`Tecla presionada: ${event.nativeEvent.key}`);

        }}

      />


      {/* 3. Eventos de ScrollView */}

      <ScrollView

        style={styles.scrollView}

        onScroll={event => {

          const y = event.nativeEvent.contentOffset.y;

          setPosicionScroll(y);

          console.log(`Scroll position: ${y}`);

        }}

        onMomentumScrollBegin={() => console.log('Scroll comenzó con momentum')}

        onMomentumScrollEnd={() => console.log('Scroll terminó con momentum')}

        onScrollBeginDrag={() => console.log('Usuario comenzó a arrastrar')}

        onScrollEndDrag={() => console.log('Usuario terminó de arrastrar')}

      >

        <View style={styles.scrollContent}>

          <Text>Desliza hacia abajo</Text>

          <Text>Posición: {posicionScroll.toFixed(0)}</Text>

          {/* Contenido largo para hacer scroll */}

          {Array.from({length: 20}).map((_, i) => (

            <Text key={i}>Elemento {i + 1}</Text>

          ))}

        </View>

      </ScrollView>


      {/* 4. Eventos de Switch */}

      <Switch

        value={esActivo}

        onValueChange={nuevoValor => {

          console.log(`Switch cambiado a: ${nuevoValor}`);

          setEsActivo(nuevoValor);

        }}

        trackColor={{false: '#767577', true: '#34C759'}}

      />


      {/* 5. Eventos personalizados con Pressable (Moderno) */}

      <Pressable

        style={({pressed}) => [

          styles.pressable,

          pressed && styles.pressablePresionado,

        ]}

        onPress={() => console.log('Pressable: onPress')}

        onPressIn={() => console.log('Pressable: onPressIn')}

        onPressOut={() => console.log('Pressable: onPressOut')}

        onLongPress={() => console.log('Pressable: onLongPress')}

      >

        {({pressed}) => (

          <Text style={styles.textoPressable}>

            {pressed ? '¡Presionado!' : 'Presionable'}

          </Text>

        )}

      </Pressable>

    </View>

  );

}


const styles = StyleSheet.create({

  container: {

    padding: 20,

  },

  boton: {

    backgroundColor: '#007AFF',

    padding: 15,

    borderRadius: 8,

    alignItems: 'center',

    marginBottom: 20,

  },

  textoBoton: {

    color: 'white',

    fontSize: 16,

    fontWeight: '600',

  },

  input: {

    borderWidth: 1,

    borderColor: '#C7C7CC',

    borderRadius: 8,

    padding: 12,

    fontSize: 16,

    marginBottom: 20,

  },

  scrollView: {

    height: 200,

    borderWidth: 1,

    borderColor: '#C7C7CC',

    borderRadius: 8,

    marginBottom: 20,

  },

  scrollContent: {

    padding: 20,

  },

  pressable: {

    backgroundColor: '#5856D6',

    padding: 15,

    borderRadius: 8,

    alignItems: 'center',

  },

  pressablePresionado: {

    backgroundColor: '#4A49B5',

    transform: [{scale: 0.95}],

  },

  textoPressable: {

    color: 'white',

    fontSize: 16,

    fontWeight: '600',

  },

});


export default ComponenteConMultiplesEventos;


🔧 Módulo 5: Patrones Avanzados de Manejo de Eventos

5.1 Pasar Parámetros a Event Handlers

tsx

// src/components/ListaInteractiva/ListaInteractiva.tsx

import React from 'react';

import {

  View,

  Text,

  TouchableOpacity,

  FlatList,

  StyleSheet,

} from 'react-native';


interface Item {

  id: string;

  nombre: string;

  descripcion: string;

}


function ListaInteractiva(): JSX.Element {

  const datos: Item[] = [

    {id: '1', nombre: 'Manzana', descripcion: 'Fruta roja'},

    {id: '2', nombre: 'Banana', descripcion: 'Fruta amarilla'},

    {id: '3', nombre: 'Naranja', descripcion: 'Fruta cítrica'},

    {id: '4', nombre: 'Uva', descripcion: 'Fruta en racimos'},

  ];


  // Manejador que recibe parámetros

  const manejarSeleccionItem = (item: Item, index: number) => {

    console.log(`Item seleccionado:`, {

      id: item.id,

      nombre: item.nombre,

      indice: index,

      timestamp: new Date().toISOString(),

    });

    

    alert(`Seleccionaste: ${item.nombre}\n${item.descripcion}`);

  };


  // Renderizar cada item

  const renderItem = ({item, index}: {item: Item; index: number}) => (

    <TouchableOpacity

      style={styles.item}

      onPress={() => manejarSeleccionItem(item, index)} // ✅ Pasar parámetros

      onLongPress={() => {

        console.log(`Long press en: ${item.nombre}`);

        alert(`Mantenido presionado: ${item.nombre}`);

      }}

    >

      <Text style={styles.nombre}>{item.nombre}</Text>

      <Text style={styles.descripcion}>{item.descripcion}</Text>

    </TouchableOpacity>

  );


  return (

    <View style={styles.container}>

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

      <FlatList

        data={datos}

        renderItem={renderItem}

        keyExtractor={item => item.id}

      />

    </View>

  );

}


const styles = StyleSheet.create({

  container: {

    flex: 1,

    padding: 20,

  },

  titulo: {

    fontSize: 24,

    fontWeight: 'bold',

    marginBottom: 20,

  },

  item: {

    backgroundColor: 'white',

    padding: 20,

    marginBottom: 10,

    borderRadius: 10,

    shadowColor: '#000',

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

    shadowOpacity: 0.1,

    shadowRadius: 4,

    elevation: 3,

  },

  nombre: {

    fontSize: 18,

    fontWeight: '600',

    marginBottom: 5,

  },

  descripcion: {

    fontSize: 14,

    color: '#666',

  },

});


export default ListaInteractiva;

5.2 Eventos con Callbacks Anidados

tsx

// src/components/FormularioInteractivo/FormularioInteractivo.tsx

import React, {useState} from 'react';

import {

  View,

  Text,

  TextInput,

  TouchableOpacity,

  Alert,

  StyleSheet,

} from 'react-native';


interface FormularioInteractivoProps {

  onSubmit: (datos: {

    nombre: string;

    email: string;

    mensaje: string;

  }) => void;

  onCancel: () => void;

}


function FormularioInteractivo({

  onSubmit,

  onCancel,

}: FormularioInteractivoProps): JSX.Element {

  const [nombre, setNombre] = useState('');

  const [email, setEmail] = useState('');

  const [mensaje, setMensaje] = useState('');


  const manejarEnvio = () => {

    // Validaciones

    if (!nombre.trim()) {

      Alert.alert('Error', 'Por favor ingresa tu nombre');

      return;

    }

    

    if (!email.trim() || !email.includes('@')) {

      Alert.alert('Error', 'Por favor ingresa un email válido');

      return;

    }


    // Preparar datos

    const datosFormulario = {

      nombre: nombre.trim(),

      email: email.trim(),

      mensaje: mensaje.trim(),

    };


    // Ejecutar callback del padre

    onSubmit(datosFormulario);


    // Limpiar formulario

    setNombre('');

    setEmail('');

    setMensaje('');

    

    console.log('Formulario enviado:', datosFormulario);

  };


  const manejarCancelar = () => {

    Alert.alert(

      'Confirmar',

      '¿Estás seguro de que quieres cancelar?',

      [

        {text: 'No', style: 'cancel'},

        {

          text: 'Sí',

          onPress: () => {

            console.log('Formulario cancelado');

            onCancel(); // Ejecutar callback del padre

          },

        },

      ],

    );

  };


  return (

    <View style={styles.container}>

      <Text style={styles.titulo}>Formulario de Contacto</Text>


      <TextInput

        style={styles.input}

        placeholder="Nombre completo"

        value={nombre}

        onChangeText={setNombre}

        onFocus={() => console.log('Campo nombre enfocado')}

      />


      <TextInput

        style={styles.input}

        placeholder="Email"

        value={email}

        onChangeText={setEmail}

        keyboardType="email-address"

        autoCapitalize="none"

        onFocus={() => console.log('Campo email enfocado')}

      />


      <TextInput

        style={[styles.input, styles.textArea]}

        placeholder="Mensaje"

        value={mensaje}

        onChangeText={setMensaje}

        multiline

        numberOfLines={4}

        onFocus={() => console.log('Campo mensaje enfocado')}

      />


      <View style={styles.botonesContainer}>

        <TouchableOpacity

          style={[styles.boton, styles.botonCancelar]}

          onPress={manejarCancelar}

          onLongPress={() =>

            console.log('Botón cancelar mantenido presionado')

          }

        >

          <Text style={styles.textoBoton}>Cancelar</Text>

        </TouchableOpacity>


        <TouchableOpacity

          style={[styles.boton, styles.botonEnviar]}

          onPress={manejarEnvio}

          onPressIn={() => console.log('Botón enviar: press in')}

          onPressOut={() => console.log('Botón enviar: press out')}

        >

          <Text style={styles.textoBoton}>Enviar</Text>

        </TouchableOpacity>

      </View>

    </View>

  );

}


const styles = StyleSheet.create({

  container: {

    padding: 20,

    backgroundColor: 'white',

    borderRadius: 12,

    shadowColor: '#000',

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

    shadowOpacity: 0.1,

    shadowRadius: 8,

    elevation: 5,

  },

  titulo: {

    fontSize: 24,

    fontWeight: 'bold',

    marginBottom: 20,

    textAlign: 'center',

  },

  input: {

    borderWidth: 1,

    borderColor: '#E5E5EA',

    borderRadius: 8,

    padding: 12,

    fontSize: 16,

    marginBottom: 15,

  },

  textArea: {

    height: 100,

    textAlignVertical: 'top',

  },

  botonesContainer: {

    flexDirection: 'row',

    justifyContent: 'space-between',

    marginTop: 20,

  },

  boton: {

    flex: 1,

    paddingVertical: 15,

    borderRadius: 8,

    alignItems: 'center',

    marginHorizontal: 5,

  },

  botonCancelar: {

    backgroundColor: '#FF3B30',

  },

  botonEnviar: {

    backgroundColor: '#34C759',

  },

  textoBoton: {

    color: 'white',

    fontSize: 16,

    fontWeight: '600',

  },

});


export default FormularioInteractivo;

5.3 Uso del Formulario en App.tsx

tsx

// App.tsx - Integración del formulario

import React, {useState} from 'react';

import {SafeAreaView, ScrollView, Text, View} from 'react-native';

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


function App(): JSX.Element {

  const [historial, setHistorial] = useState<Array<{

    nombre: string;

    email: string;

    mensaje: string;

    fecha: string;

  }>>([]);


  const manejarEnvioFormulario = (datos: {

    nombre: string;

    email: string;

    mensaje: string;

  }) => {

    const nuevoRegistro = {

      ...datos,

      fecha: new Date().toLocaleString(),

    };

    

    setHistorial([nuevoRegistro, ...historial]);

    

    Alert.alert(

      '¡Éxito!',

      `Formulario enviado por ${datos.nombre}`,

      [{text: 'OK'}],

    );

  };


  const manejarCancelarFormulario = () => {

    console.log('Formulario cancelado desde App');

    // Puedes agregar más lógica aquí

  };


  return (

    <SafeAreaView style={{flex: 1}}>

      <ScrollView contentContainerStyle={{padding: 20}}>

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

        

        <FormularioInteractivo

          onSubmit={manejarEnvioFormulario}

          onCancel={manejarCancelarFormulario}

        />


        {historial.length > 0 && (

          <View style={styles.historial}>

            <Text style={styles.subtitulo}>Historial de Envíos</Text>

            {historial.map((item, index) => (

              <View key={index} style={styles.registro}>

                <Text style={styles.registroNombre}>{item.nombre}</Text>

                <Text style={styles.registroEmail}>{item.email}</Text>

                <Text style={styles.registroFecha}>{item.fecha}</Text>

              </View>

            ))}

          </View>

        )}

      </ScrollView>

    </SafeAreaView>

  );

}


const styles = {

  titulo: {

    fontSize: 28,

    fontWeight: 'bold',

    marginBottom: 30,

    textAlign: 'center',

  },

  historial: {

    marginTop: 30,

    padding: 20,

    backgroundColor: '#F5F5F7',

    borderRadius: 12,

  },

  subtitulo: {

    fontSize: 20,

    fontWeight: '600',

    marginBottom: 15,

  },

  registro: {

    backgroundColor: 'white',

    padding: 15,

    borderRadius: 8,

    marginBottom: 10,

  },

  registroNombre: {

    fontSize: 16,

    fontWeight: 'bold',

  },

  registroEmail: {

    fontSize: 14,

    color: '#666',

  },

  registroFecha: {

    fontSize: 12,

    color: '#999',

    fontStyle: 'italic',

  },

};


export default App;


🎯 Módulo 6: Mejores Prácticas y Patrones

6.1 Patrones Recomendados para Eventos

tsx

// src/components/PatronesEventos/PatronesEventos.tsx

import React from 'react';

import {

  View,

  Text,

  TouchableOpacity,

  Alert,

  StyleSheet,

} from 'react-native';


function PatronesEventos(): JSX.Element {

  // ✅ PATRÓN 1: Función nombrada (mejor para debugging)

  const manejarPresionNombrada = () => {

    console.log('Función nombrada ejecutada');

    Alert.alert('Éxito', 'Función nombrada llamada');

  };


  // ✅ PATRÓN 2: Arrow function para parámetros

  const crearManejadorConParametro = (parametro: string) => () => {

    console.log(`Parámetro recibido: ${parametro}`);

  };


  // ✅ PATRÓN 3: Event handler con validación

  const manejarConValidacion = (dato: string) => {

    if (!dato.trim()) {

      console.warn('Dato vacío recibido');

      return;

    }

    console.log(`Dato válido: ${dato}`);

  };


  // ✅ PATRÓN 4: Event handler que retorna otra función

  const crearLogger = (contexto: string) => {

    return () => {

      console.log(`Evento desde: ${contexto}`, {

        hora: new Date().toISOString(),

        contexto,

      });

    };

  };


  // ❌ PATRÓN A EVITAR: Inline function compleja

  const patronMalo = () => {

    return (

      <TouchableOpacity

        onPress={() => {

          // ❌ Demasiada lógica inline

          const resultado = Math.random() * 100;

          console.log(resultado);

          if (resultado > 50) {

            Alert.alert('Mayor a 50');

          } else {

            Alert.alert('Menor a 50');

          }

        }}

      >

        <Text>Patrón a evitar</Text>

      </TouchableOpacity>

    );

  };


  return (

    <View style={styles.container}>

      <Text style={styles.titulo}>Patrones de Manejo de Eventos</Text>


      {/* Patrón 1: Función nombrada */}

      <TouchableOpacity

        style={styles.boton}

        onPress={manejarPresionNombrada}

      >

        <Text style={styles.textoBoton}>Función Nombrada</Text>

      </TouchableOpacity>


      {/* Patrón 2: Arrow function con parámetro */}

      <TouchableOpacity

        style={styles.boton}

        onPress={crearManejadorConParametro('miParametro')}

      >

        <Text style={styles.textoBoton}>Con Parámetro Predefinido</Text>

      </TouchableOpacity>


      {/* Patrón 3: Con validación */}

      <TouchableOpacity

        style={styles.boton}

        onPress={() => manejarConValidacion('Dato de prueba')}

      >

        <Text style={styles.textoBoton}>Con Validación</Text>

      </TouchableOpacity>


      {/* Patrón 4: Factory function */}

      <TouchableOpacity

        style={styles.boton}

        onPress={crearLogger('componentePatrones')}

      >

        <Text style={styles.textoBoton}>Factory Function</Text>

      </TouchableOpacity>

    </View>

  );

}


const styles = StyleSheet.create({

  container: {

    padding: 20,

  },

  titulo: {

    fontSize: 24,

    fontWeight: 'bold',

    marginBottom: 20,

  },

  boton: {

    backgroundColor: '#5856D6',

    padding: 15,

    borderRadius: 8,

    alignItems: 'center',

    marginBottom: 15,

  },

  textoBoton: {

    color: 'white',

    fontSize: 16,

    fontWeight: '600',

  },

});

6.2 Reglas de Oro para Manejo de Eventos

  1. Separa la lógica de los handlers de la definición del componente

  2. Usa funciones nombradas para mejor debugging

  3. Evita lógica compleja inline en los handlers

  4. Tipa correctamente los handlers con TypeScript

  5. Maneja errores dentro de los event handlers

  6. Considera performance con handlers en listas grandes

  7. Usa debouncing/throttling para eventos frecuentes

  8. Limpia event listeners en Class Components


📊 Módulo 7: Ejercicios Prácticos

Ejercicio 1: Contador Interactivo

Crea un componente de contador con:

  • Botón para incrementar

  • Botón para decrementar

  • Botón para resetear

  • Display que muestre el valor actual

  • Eventos para cada acción

Ejercicio 2: Lista de Tareas

Crea una lista de tareas interactiva con:

  • Añadir nueva tarea (TextInput + Botón)

  • Marcar tarea como completada (onPress)

  • Eliminar tarea (onLongPress o swipe)

  • Editar tarea (doble tap)

Ejercicio 3: Galería de Imágenes

Crea una galería con:

  • Miniaturas que se pueden presionar

  • Vista ampliada al presionar una imagen

  • Botones de navegación (anterior/siguiente)

  • Evento para cerrar la vista ampliada


🎓 Evaluación Final

Preguntas de Conocimiento:

  1. ¿Cuál es la diferencia entre onPress y onLongPress?

  2. ¿Cómo pasarías parámetros a un event handler?

  3. ¿Cuándo usarías TouchableOpacity vs Pressable?

  4. ¿Cómo manejarías eventos en listas grandes para optimizar performance?

  5. ¿Qué es el event bubbling y cómo funciona en React Native?

Proyecto Final:

Crea una aplicación de notas con:

  • Crear nueva nota (con título y contenido)

  • Listar todas las notas

  • Editar nota existente

  • Eliminar nota (con confirmación)

  • Buscar notas por texto

  • Cada acción debe usar eventos apropiados


🎉 Conclusión del Curso

Has aprendido a dominar los eventos en React Native:

✅ Fundamentos de eventos y handlers
✅ Componentes táctiles (TouchableOpacity, Pressable)
✅ Manejo de eventos en Functional y Class Components
✅ Tipos de eventos (onPress, onChangeText, onScroll, etc.)
✅ Patrones avanzados y mejores prácticas


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