Typescript
hooks
react native
mobile development
React Native nos permite crear aplicaciones móviles nativas con la simplicidad declarativa de React. Cuando agregamos TypeScript, ganamos claridad y seguridad: sabemos qué tipo de datos maneja cada componente, y el editor nos ayuda a evitar errores en tiempo real.
En este artículo aprenderás tres pilares esenciales del desarrollo moderno con React Native:
Tanto useState como useEffect son hooks, funciones que reciben un valor y/o un "callback" (el cual ejecutan en circunstancias especificas, por ejemplo: cuando cambia un valor). Estas funciones pueden, además, devolver valores y otros callbacks. Los hooks son la base de la programación moderna en React.
useStateAntes de entender useState, necesitamos imaginar cómo piensa React. Cada componente es como una pequeña máquina de estados; cuando esos datos cambian, la interfaz debe cambiar automáticamente.
En una app móvil real, el estado puede representar muchas cosas:
En React, estos valores cambiantes se guardan con el hook useState. Este hook nos permite recordar datos entre renderizados y actualizarlos de forma reactiva, sin manipular la UI manualmente.
1import React, { useState } from 'react'; 2import { View, Text, Button } from 'react-native'; 3 4export default function Counter(): JSX.Element { 5 const [count, setCount] = useState<number>(0); 6 7 return ( 8 <View style={{ padding: 20 }}> 9 <Text style={{ fontSize: 24 }}>Has hecho clic {count} veces</Text> 10 <Button title="Incrementar" onPress={() => setCount(count + 1)} /> 11 </View> 12 ); 13}
Explicación
useState<number>(0) crea una variable de estado count que solo puede ser un número.setCount funciona como método "set": actualiza el estado y provoca un re-render automático.💡 En Kotlin, esto sería similar a usar un MutableStateFlow<Int> o un LiveData<Int> observado por la UI. En Swift (SwiftUI) el equivalente seria el "property wrapper" @State
Si deseas actualizar un estado en base a un valor que tenia anteriormente, se debe utilizar una sintaxis distinta al momento de llamar al método "set", de lo contrario obtendrás valores desactualizados
1 2const [wrongCount, setWrongCount] = useState(0); 3const [rightCount, setRightCount] = useState(0) 4 5const updateCount = () => { 6 setWrongCount(wrongCount + 1) // Valor no estara actualizado, por ende el resultado no será el esperado 7 8 setRightCount((prevCount) => { 9 return prevCount + 1 10 }) // Valor siempre estará actualizado. prevCount almacena el valor antes del update 11} 12
useEffectReact mantiene una referencia a cada prop y estado existente dentro de un componente: cuando cambia, React es capaz de "reaccionar" a este cambio, y ejecutar lógica en base al nuevo valor. Para ello, podemos usar el hook useEffect
1useEffect( 2 () => { 3 // logica 4 }, 5 [valor1, valor2] 6) 7 8// Cada vez que "valor1" o "valor2" se actualiza, logica se efecuta
Además, este hook puede devolver una función la cual se ejecutará cada vez que el componente se desmonte. Esta función es util para des-suscribirse de listeners, o eliminar referencias temporales (como timers)
1 2useEffect( 3 () => { 4 const timer = setTimeout( 5 () => { 6 // logica 7 }, 8 1000 9 ) 10 return () => { 11 // En caso el componente se desmonte antes de que se cumpla el segundo, borramos la referencia al timer 12 clearTimeout(timer) 13 } 14 }, 15 [] 16)
useEffectSi useEffect se utiliza sin pasar ninguna dependencia, React lo ejecutará 1 vez (2 si estás en StrictMode) después de que el componente se renderiza . Es el equivalente a onCreate y onDestroy en Android, o viewDidLoad y viewDidDisappear en iOS, pero expresado de forma declarativa.
1import React, { useEffect } from 'react'; 2import { View, Text } from 'react-native'; 3 4export default function SimpleEffect(): JSX.Element { 5 useEffect(() => { 6 console.log('🟢 Componente montado'); 7 8 return () => { 9 console.log('🔴 Componente desmontado'); 10 }; 11 }, []); 12 13 return ( 14 <View style={{ padding: 20 }}> 15 <Text style={{ fontSize: 20 }}>Observa la consola</Text> 16 </View> 17 ); 18}
Qué ocurre
return).Equivalencias conceptuales:
| Android | React Native | Swift |
|---|---|---|
onCreate() | useEffect(() => {...}, []) | viewDidLoad() |
onDestroy() | return () => {...} | viewDidDisappear() |
1import React, { useState, useEffect } from 'react'; 2import { View, Text } from 'react-native'; 3 4export default function Timer(): JSX.Element { 5 const [seconds, setSeconds] = useState<number>(0); 6 7 useEffect(() => { 8 console.log('⏱ Temporizador iniciado'); 9 10 const interval = setInterval(() => { 11 setSeconds((prev) => prev + 1); 12 }, 1000); 13 14 return () => { 15 console.log('🧹 Temporizador detenido'); 16 clearInterval(interval); 17 }; 18 }, []); 19 20 return ( 21 <View style={{ padding: 20 }}> 22 <Text style={{ fontSize: 22 }}>Segundos: {seconds}</Text> 23 </View> 24 ); 25}
En Kotlin sería:
1override fun onCreate(savedInstanceState: Bundle?) { 2 timer = Timer() 3 timer.scheduleAtFixedRate(object : TimerTask() { 4 override fun run() { 5 seconds++ 6 } 7 }, 0, 1000) 8} 9 10override fun onDestroy() { 11 timer.cancel() 12}
Y en Swift:
1override func viewDidLoad() { 2 timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in 3 seconds += 1 4 } 5} 6 7override func viewDidDisappear(_ animated: Bool) { 8 timer.invalidate() 9}
🪄 En React Native lo haces con una sola estructura, más declarativa y predecible.
1import React, { useState, useEffect } from 'react'; 2import { View, Text, Button } from 'react-native'; 3 4export default function DependentEffect(): JSX.Element { 5 const [count, setCount] = useState<number>(0); 6 7 useEffect(() => { 8 console.log(`El contador cambió a: ${count}`); 9 }, [count]); // solo se ejecuta cuando cambia count 10 11 return ( 12 <View style={{ padding: 20 }}> 13 <Text style={{ fontSize: 20 }}>Contador: {count}</Text> 14 <Button title="Incrementar" onPress={() => setCount(count + 1)} /> 15 </View> 16 ); 17}
💡 En Kotlin podrías compararlo con observar un LiveData<Int> y en Swift con una propiedad didSet.
El renderizado condicional permite mostrar diferentes vistas según el estado.
1import React, { useState } from 'react'; 2import { View, Text, Button } from 'react-native'; 3 4export default function LoginExample(): JSX.Element { 5 const [loggedIn, setLoggedIn] = useState<boolean>(false); 6 7 return ( 8 <View style={{ padding: 20 }}> 9 {loggedIn ? ( 10 <> 11 <Text style={{ fontSize: 20 }}>¡Bienvenido de nuevo!</Text> 12 <Button title="Cerrar sesión" onPress={() => setLoggedIn(false)} /> 13 </> 14 ) : ( 15 <> 16 <Text style={{ fontSize: 20 }}>Por favor, inicia sesión</Text> 17 <Button title="Iniciar sesión" onPress={() => setLoggedIn(true)} /> 18 </> 19 )} 20 </View> 21 ); 22}
Esto es similar a usar un if o un when en Kotlin, pero dentro del propio JSX, mezclando lógica y presentación.
Para renderizar listas en React Native se usa FlatList, optimizada para rendimiento.
1import React from 'react'; 2import { View, Text, FlatList } from 'react-native'; 3 4interface Task { 5 id: string; 6 text: string; 7} 8 9export default function TodoList(): JSX.Element { 10 const tasks: Task[] = [ 11 { id: '1', text: 'Aprender useState' }, 12 { id: '2', text: 'Practicar useEffect' }, 13 { id: '3', text: 'Dominar el renderizado condicional' }, 14 ]; 15 16 return ( 17 <View style={{ padding: 20 }}> 18 <FlatList 19 data={tasks} 20 keyExtractor={(item) => item.id} 21 renderItem={({ item }) => ( 22 <Text style={{ fontSize: 18, marginVertical: 4 }}>• {item.text}</Text> 23 )} 24 /> 25 </View> 26 ); 27}
| Concepto | Kotlin | Swift / SwiftUI | React Native |
|---|---|---|---|
| Estado | LiveData, MutableStateFlow | @State, @Published | useState |
| Efectos de ciclo de vida | onCreate(), onDestroy() | .onAppear {}, .onDisappear {} | useEffect |
| Vista condicional | if, when | if, switch | {condición ? <A/> : <B/>} |
| Listas dinámicas | RecyclerView | List | FlatList |
React Native con TypeScript te enseña a pensar en estados y efectos, no en instrucciones. El flujo de datos es unidireccional, y los componentes reaccionan automáticamente a los cambios, sin que tú tengas que “decirles qué hacer”.