← Back to Lessons

State, Lifecycle, and Dynamic Rendering in React Native with TypeScript

Local State with useState
  • Basic Example with TypeScript

React Native lets us build native mobile applications with the declarative simplicity of React. By adding TypeScript, we gain clarity and safety: we know what type of data each component handles, and the editor helps us avoid errors in real time.

In this article, you'll learn three essential pillars of modern React Native development:

  1. useState — local state management
  2. useEffect — lifecycle control
  3. Conditional rendering and lists

Local State with useState

Before understanding useState, we need to imagine how React thinks. Each component is like a small state machine; when those data change, the UI should automatically update.

In a real mobile app, state can represent many things:

  • The number of times a user presses a button.
  • The text being typed in a form field.
  • Whether a user is logged in or not.
  • A switch being on or off.

In React, these changing values are stored with the useState hook. This hook lets us remember data between renders and update them reactively, without manually manipulating the UI.

Basic Example with 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 }}>You clicked {count} times</Text> 10 <Button title="Increment" onPress={() => setCount(count + 1)} /> 11 </View> 12 ); 13}

Explanation

  • useState<number>(0) creates a state variable count that can only be a number.
  • setCount updates the state and triggers an automatic re-render.
  • Each click displays the new value on screen.

💡 In Kotlin, this is similar to using a MutableStateFlow<Int> or a LiveData<Int> observed by the UI.

Lifecycle with useEffect

useEffect is used to run code after the component renders, or when certain values change. It's the equivalent of onCreate and onDestroy in Android, or viewDidLoad and viewDidDisappear in iOS, but expressed declaratively.

Example 1: Mount and Unmount

1import React, { useEffect } from 'react'; 2import { View, Text } from 'react-native'; 3 4export default function SimpleEffect(): JSX.Element { 5 useEffect(() => { 6 console.log('🟢 Component mounted'); 7 8 return () => { 9 console.log('🔴 Component unmounted'); 10 }; 11 }, []); 12 13 return ( 14 <View style={{ padding: 20 }}> 15 <Text style={{ fontSize: 20 }}>Check the console</Text> 16 </View> 17 ); 18}

What happens

  • When the component appears, the first block runs.
  • When it disappears, the cleanup function (return) runs.

Conceptual equivalents:

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

Example 2: Timer with Cleanup

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('⏱ Timer started'); 9 10 const interval = setInterval(() => { 11 setSeconds((prev) => prev + 1); 12 }, 1000); 13 14 return () => { 15 console.log('🧹 Timer stopped'); 16 clearInterval(interval); 17 }; 18 }, []); 19 20 return ( 21 <View style={{ padding: 20 }}> 22 <Text style={{ fontSize: 22 }}>Seconds: {seconds}</Text> 23 </View> 24 ); 25}

In Kotlin:

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}

And in 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}

🪄 In React Native you do this with a single structure, more declarative and predictable.

Example 3: Effect Dependent on a Value

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(`Counter changed to: ${count}`); 9 }, [count]); // only runs when count changes 10 11 return ( 12 <View style={{ padding: 20 }}> 13 <Text style={{ fontSize: 20 }}>Counter: {count}</Text> 14 <Button title="Increment" onPress={() => setCount(count + 1)} /> 15 </View> 16 ); 17}

💡 In Kotlin, you could compare this to observing a LiveData<Int>, and in Swift to a property didSet.

3. Conditional Rendering and Lists

Conditional rendering lets you show different views based on state.

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 }}>Welcome back!</Text> 12 <Button title="Log out" onPress={() => setLoggedIn(false)} /> 13 </> 14 ) : ( 15 <> 16 <Text style={{ fontSize: 20 }}>Please log in</Text> 17 <Button title="Log in" onPress={() => setLoggedIn(true)} /> 18 </> 19 )} 20 </View> 21 ); 22}

This is similar to using an if or when in Kotlin, but inside JSX itself, mixing logic and presentation.

Dynamic Lists

To render lists in React Native, use FlatList, optimized for performance.

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: 'Learn useState' }, 12 { id: '2', text: 'Practice useEffect' }, 13 { id: '3', text: 'Master conditional rendering' }, 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}

In Summary

ConceptKotlinSwift / SwiftUIReact Native
StateLiveData, MutableStateFlow@State, @PublisheduseState
Lifecycle effectsonCreate(), onDestroy().onAppear {}, .onDisappear {}useEffect
Conditional viewif, whenif, switch{condition ? <A/> : <B/>}
Dynamic listsRecyclerViewListFlatList

React Native with TypeScript teaches you to think in states and effects, not instructions. The data flow is unidirectional, and components automatically react to changes, without you having to “tell them what to do”.