← Regresar a lecciones

Estado, ciclo de vida y renderizado dinámico en React Native con TypeScript

Estado local con useState

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:

  1. useState — manejo de estado local
  2. useEffect — control del ciclo de vida
  3. Renderizado condicional y listas

Sobre los hooks

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.

Estado local con useState

Antes 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:

  • El número de veces que un usuario presiona un botón.
  • El texto que está escribiendo en un campo de formulario.
  • Si un usuario está o no logueado.
  • Un interruptor encendido o apagado.

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.

Ejemplo básico con TypeScript

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.
  • Cada clic muestra el nuevo valor en pantalla.

💡 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

Actualizando estado en base a estados anteriores

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

"Reaccionando" a cambios con useEffect

React 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)

Ciclo de vida con useEffect

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

Ejemplo 1: montaje y desmontaje

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

  • Al aparecer el componente, se ejecuta el primer bloque.
  • Al desaparecer, se ejecuta la función de limpieza (return).

Equivalencias conceptuales:

AndroidReact NativeSwift
onCreate()useEffect(() => {...}, [])viewDidLoad()
onDestroy()return () => {...}viewDidDisappear()

Ejemplo 2: temporizador con limpieza

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.

Ejemplo 3: efecto dependiente de un valor

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.

3. Renderizado condicional y listas

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.

Listas dinámicas

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}

En resumen

ConceptoKotlinSwift / SwiftUIReact Native
EstadoLiveData, MutableStateFlow@State, @PublisheduseState
Efectos de ciclo de vidaonCreate(), onDestroy().onAppear {}, .onDisappear {}useEffect
Vista condicionalif, whenif, switch{condición ? <A/> : <B/>}
Listas dinámicasRecyclerViewListFlatList

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