React Native Basics
Based on the NetNinja Course
Updated: 23 December 2025
From the Net Ninja’s React-Native Course on YouTube
Introduction
React native allows us to use React to develop native mobile applications. We’re going through this course using the Expo CLI along with Typescript instead of just plain RN with JS
Init App
To create a new RN app we use the expo-cli using the following:
1npm install -g expo-cli2expo init rn-net-ninjaAnd select the blank (TypeScript) as the application type. Then, cd into the created directory and run yarn start
1cd rn-net-ninja2yarn startYou can then scan the barcode from the terminal via the Expo Go app which you can download from the app store on your test device
In the scaffolded code you will see the following structure:
1rn-net-ninja2 |- .expo3 |- .expo-shared4 |- assets5 |- app.json6 |- App.tsx7 |- babel.config.js8 |- package.json9 |- tsconfig.json10 |- yarn.lockViews and Styles
The App.tsx file contains a View with some Text and a StyleSheet
App.tsx
1import { StatusBar } from 'expo-status-bar'2import React from 'react'3import { StyleSheet, Text, View } from 'react-native'4
5export default function App() {6 return (7 <View style={styles.container}>8 <Text>Hello, World!</Text>9 <StatusBar style="auto" />10 </View>11 )12}13
14const styles = StyleSheet.create({15 container: {16 flex: 1,17 backgroundColor: '#fff',18 alignItems: 'center',19 justifyContent: 'center',20 },21})In RN we use the Text component to represent any plain text view as we would in typical JSX, additionally, the View can be likened to a div and the StyleSheet to a CSS File
In terms of the way RN uses styles it uses a CSS-like API but doesn’t actually render to CSS but does magic in the background to get these to be like native styles
An important distinction when compared CSS is that RN styles aren’t inherited by children elements, this means that if we want to apply a specific font to each component for example, we would actually have to apply this to every element individually
Text Inputs
RN Provides some other basic components, one of these are the TextInput component which we can use like so:
1export default function App() {2 const [name, setName] = useState<string>("Bob");3
4 const handleTextChange = useCallback(setName, [name]);5
6 return (7 <View style={styles.inputWrapper}>8 <Text style={styles.inputLabel}>Enter Name</Text>9 <TextInput10 style={styles.input}11 value={name}12 onChangeText={handleTextChange}13 />14 );15}Lists and ScrollView
ScrollView allows us to render a list of items in a scrollable area, this can be rendered as follows:
1// people: { id: number; name: string; age: number;}[]2<ScrollView>3 {people.map((p, i) => (4 <View style={styles.person} key={i}>5 <Text>{p.name}</Text>6 <Text>{p.age}</Text>7 </View>8 ))}9</ScrollView>FlatList
Additionally, there’s another way to render a scrollable list of elements: using a FlatList which looks like so:
1// people: { id: number; name: string; age: number;}[]2<FlatList3 data={people}4 renderItem={({ item }) => (5 <View style={styles.person}>6 <Text>{item.name}</Text>7 <Text>{item.age}</Text>8 </View>9 )}10 keyExtractor={({ id }) => id.toString()}11/>Additionally, FlatList also supports a numColumns prop which lets us set the number of columns to use for the layout
The FlatList will only render the components as they move into view, and this is useful when we have a really long list of items that we want to render
Pressable
We can use the Pressable component to make any element able to handle touch events, for example:
In general, we can surround an element by Pressable and then handle the onPress event:
1<Pressable onPress={() => onPersonSelected(item)}>2 <View style={styles.person}>3 <Text>{item.name}</Text>4 <Text>{item.age}</Text>5 </View>6</Pressable>And then, somewhere higher up we can have onPersonSelected defined:
1const onPersonSelected = useCallback<PersonSelectedCallback>(2 (p) => setName(p.name),3 []4)We can set some app props by applying this to the FlatList components above.
Without too much detail, some of the types being referenced by our setup are:
1interface Person {2 id: number3 name: string4 age: number5}6
7type PersonSelectedCallback = (p: Person) => voidAnd our person display component will then look like this:
1const PeopleView: React.FC<{ onPersonSelected: PersonSelectedCallback }> =2 function ({ onPersonSelected }) {3 const people = [4 { name: 'Jeff', age: 1 },5 { name: 'Bob', age: 2 },6 // ...7 ].map((p, i) => ({ ...p, id: i }))8
9 return (10 <FlatList11 data={people}12 renderItem={({ item }) => (13 <Pressable onPress={() => onPersonSelected(item)}>14 <View style={styles.person}>15 <Text>{item.name}</Text>16 <Text>{item.age}</Text>17 </View>18 </Pressable>19 )}20 keyExtractor={({ id }) => id.toString()}21 />22 )23 }Keyboard Dismissing
We’ll notice that the way the app works at the moment, some basic interactions, such as the keyboard automatically being dismissed don’t work. In order to do this we need to add a handler onto the component which causes the keyboard t be dismissed.
This can be done using a combination of a Pressable or TouchableWithoutFeedback and then Keyboard.dismiss() in the onPress handler
Flexbox
Just a short note here - Views in RN behave like flexboxes, so we can do normal flexboxy things for layout in our components.
Something that’s commonly done is to use flex: 1 on components so that they fill the available space on a page. This is especially useful for limiting the size of a ScrollView or FlatList
Additionally, applying flex: 1 to the root component will cause the app to fill our entire screen
Fonts
To use custom fonts you can make use of the Expo CDK, here’s the doc
1expo install expo-fontAnd we can use a font from our app with the useFonts hook:
1import * as React from 'react'2import { View, Text, StyleSheet } from 'react-native'3import { useFonts } from 'expo-font'4
5export default function App() {6 const [loaded] = useFonts({7 koho: require('./assets/fonts/KoHo-Regular.ttf'),8 })9
10 if (!loaded) {11 return null12 }13
14 return (15 <View style={styles.container}>16 <Text style={{ fontFamily: 'koho', fontSize: 30 }}>KoHo</Text>17 </View>18 )19}React Navigation
We’re going to use the react-navigation library for building out the navigation and routing.
We can to install the required packages with:
1yarn add @react-navigation/native
expo installdoes some additional version checks, etc.
1expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-viewAdditionally, react-navigation supports a few different types of navigation. For our first case we’re going to use a Stack navigation. So we need to install that with:
1yarn add @react-navigation/stackStack Navigator
We can create a Stack Navigator using some of the constructs provided by react-navigation. These work by using createStackNavigator and composing the navigation within a NavigationContainer. Note that we also use the AppStackParamList to list the params required for each screen
routes/HomeNavigationContainer.tsx
1import React from 'react'2import { createStackNavigator } from '@react-navigation/stack'3import { NavigationContainer } from '@react-navigation/native'4import Home from '../screens/Home'5import ReviewDetails from '../screens/ReviewDetails'6
7export type AppStackParamList = {8 Home: undefined9 ReviewDetails: undefined10}11
12const Stack = createStackNavigator<AppStackParamList>()13
14export default function HomeNavigationContainer() {15 return (16 <NavigationContainer>17 <Stack.Navigator initialRouteName="Home">18 <Stack.Screen name="Home" component={Home} />19 <Stack.Screen name="ReviewDetails" component={ReviewDetails} />20 </Stack.Navigator>21 </NavigationContainer>22 )23}We can then use this from the App component with:
App.tsx
1// other imports2import HomeNavigationContainer from './routes/HomeNavigationContainer'3
4export default function App() {5 // other stuff6
7 return <HomeNavigationContainer />8}Navigating
In order to navigate we need to use the navigation prop that’s passed to our component by react-navigation. In order to do this we need to configure our screen to use the AppStackParamList type with StackScreenProps
screens/Home.tsx
1import { StackScreenProps } from '@react-navigation/stack'2import React from 'react'3import { View, Text, Button } from 'react-native'4import { AppStackParamList } from '../routes/HomeNavigationContainer'5import { globalStyles } from '../styles'6
7type HomeProps = StackScreenProps<AppStackParamList, 'Home'>8
9const Home: React.FC<HomeProps> = function ({ navigation }) {10 const navigateToReviews = () => {11 navigation.navigate('ReviewDetails')12 }13
14 return (15 <View style={globalStyles.container}>16 <Text style={globalStyles.titleText}>Home Page</Text>17 <Button title="To Reviews" onPress={navigateToReviews} />18 </View>19 )20}21
22export default HomeSend data to screen
We can send some data to each screen by defining the data in our routing params:
1export type AppStackParamList = {2 Home: undefined3 ReviewDetails: { title: string; rating: number; body: string }4}And then our Reviews screen will look like this:
1type ReviewDetailsProps = StackScreenProps<AppStackParamList, "ReviewDetails">;2
3const ReviewDetails: React.FC<ReviewDetailsProps> = function ({ navigation, route }) {4 const params = route.params5
6 // ... render stuff, etc.And lastly, we can update the Home component to send the data that this screen requires using the navigator.navigate function:
1const Home: React.FC<HomeProps> = function ({ navigation }) {2 const navigateToReviews = () => {3 navigation.navigate("ReviewDetails", { // screen props/data4 title: 'that racing movie',5 rating: 1,6 body: 'it was terrible'7 });8 };9
10 // ... render stuff, etc.Drawer Navigation
Now, since we’ve got all our navigation working, we’re going to add some complexity by including a drawer based navigation that wraps our overall application. To do this we will need to change when we’re using our stack and creating the NavigationContainer
To do this, we’ll first return just the stack from the HomeNavigationContainer and then create the container at the App component level. It also may be useful to rename the HomeNavigationContainer file to HomeStack.tsx
routes/HomeStack.tsx
1import { StackScreenProps } from '@react-navigation/stack'2import React from 'react'3import { View, Text, Button } from 'react-native'4import { AppStackParamList } from '../routes/HomeStack'5import { globalStyles } from '../styles'6
7type HomeProps = StackScreenProps<AppStackParamList, 'Home'>8
9const Home: React.FC<HomeProps> = function ({ navigation }) {10 const navigateToReviews = () => {11 navigation.navigate('ReviewDetails', {12 title: 'that racing movie',13 rating: 1,14 body: 'it was terrible',15 })16 }17
18 return (19 <View style={globalStyles.container}>20 <Text style={globalStyles.titleText}>Home Page</Text>21 <Button title="To Reviews" onPress={navigateToReviews} />22 </View>23 )24}25
26export default HomeNext, we need to add the About page content into a stack, like so:
routes/AboutStack.tsx
1import React from 'react'2import { createStackNavigator } from '@react-navigation/stack'3import { NavigationContainer } from '@react-navigation/native'4import About from '../screens/About'5
6export type AppStackParamList = {7 About: undefined8}9
10const Stack = createStackNavigator<AppStackParamList>()11
12export default function AboutStack() {13 return (14 <Stack.Navigator initialRouteName="About">15 <Stack.Screen name="About" component={About} />16 </Stack.Navigator>17 )18}Then, we will include these components/stacks as the component that needs to be rendered. Since we’re using a DrawerNavigator, we do this by first creating the navigator, then providind our screens as the routes:
routes/DrawerNavigator.tsx
1import React from 'react'2import {3 createDrawerNavigator,4 DrawerScreenProps,5} from '@react-navigation/drawer'6import HomeStack from './HomeStack'7import AboutStack from './AboutStack'8import { NavigationContainer } from '@react-navigation/native'9
10type DrawerParamList = {11 Home: undefined12 About: undefined13}14
15const Navigator = createDrawerNavigator<DrawerParamList>()16
17export default function DrawerNavigator() {18 return (19 <NavigationContainer>20 <Navigator.Navigator drawerType="front">21 <Navigator.Screen component={HomeStack} name="Home" />22 <Navigator.Screen component={AboutStack} name="About" />23 </Navigator.Navigator>24 </NavigationContainer>25 )26}And lastly, we can use this from the App.tsx file like so:
App.tsx
1import React from 'react'2import { useFonts } from 'expo-font'3import AppLoading from 'expo-app-loading'4import { Stack } from './routes/HomeStack'5import { NavigationContainer } from '@react-navigation/native'6import Home from './screens/Home'7import ReviewDetails from './screens/ReviewDetails'8import DrawerNavigator from './routes/DrawerNavigator'9
10export default function App() {11 const [loaded] = useFonts({12 'koho-regular': require('./assets/fonts/KoHo-Regular.ttf'),13 })14
15 if (!loaded) {16 return <AppLoading />17 }18
19 return <DrawerNavigator />20}Now that we’ve got the drawer working, it’s a matter of finding a way to trigger it from our code, we can use a custom header component to do this. A special consideration here is that this component should have a definition for a composite navigation type in order to access and work with the drawer:
1import { useNavigation } from '@react-navigation/core'2import {3 DrawerNavigationProp,4 DrawerScreenProps,5 useIsDrawerOpen,6} from '@react-navigation/drawer'7import { CompositeNavigationProp } from '@react-navigation/native'8import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack'9import React from 'react'10import { View, StyleSheet, Text, Button } from 'react-native'11import { DrawerParamList } from '../routes/DrawerNavigator'12
13// composite navigation type to state that the screen14// is within two types of navigators15type HeaderScreenNavigationProp = CompositeNavigationProp<16 StackNavigationProp<DrawerParamList, 'Home'>,17 DrawerNavigationProp<DrawerParamList>18>19
20const Header = function () {21 const navigation = useNavigation<HeaderScreenNavigationProp>()22
23 function openDrawer() {24 navigation.openDrawer()25 }26
27 return (28 <View style={styles.header}>29 <Button title="Menu" onPress={openDrawer} />30 <Text>Header Text</Text>31 </View>32 )33}34
35export default HeaderWe can then implement the Header in the HomeStack component in the options param for the stack:
routes/HomeStack.tsx
1export default function HomeStack() {2 return (3 <Stack.Navigator initialRouteName="Home">4 <Stack.Screen5 name="Home"6 component={Home}7 options={{8 header: Header,9 }}10 />11 <Stack.Screen name="ReviewDetails" component={ReviewDetails} />12 </Stack.Navigator>13 )14}And the exact same for About
routes/AboutStack.tsx
1export default function AboutStack() {2 return (3 <Stack.Navigator initialRouteName="About">4 <Stack.Screen5 name="About"6 component={About}7 options={{8 header: () => <Header />,9 }}10 />11 </Stack.Navigator>12 )13}Custom Title for Stack-Generate Header
Using the Stack Navigation, we are also provided with a title, this is still used by the ReviewDetails screen as it doesnt have any additional options. In addition to the options param as an object, we can also set it to a function which calculates the values from the data shared to the component. So we can use the title prop from our component to set this:
routes/HomeStack.tsx
1<Stack.Screen2 name="ReviewDetails"3 component={ReviewDetails}4 options={({ route }) => ({5 title: route.params.title,6 })}7/>Images
To use images in React-Native, we make use of the Image component with the source prop with a require call
1import { Image } from 'react-native'2
3//... do stuff4
5return <Image source={require('../assets/my-image.png')}>Note that the require data must be a constant string, so we can’t calculate this on the fly
If we have a set of images that we would like to dynamically select from, we can however still do something like this:
1const images = {2 car: require('../assets/car.png'),3 bike: require('../assets/bike.png'),4 truck: require('../assets/truck.png'),5 boat: require('../assets/boat.png'),6}1const selectedImage = 'truck'2
3return <Image source={images[selectedImage]}>Additionally, if we want to call an image from a URL, we can do this as we normally would, for example:
1const imageUrl = getUserProfileUrl('my-user')2
3return <Image source={imageUrl}>BackgroundImage
In RN we can’t set image backgrounds using a normal background style prop, instead we need to use a BackgroundImage component from react-native, this works similar to the Image component but with the element we’re adding the background to contained in it
1<BackgroundImage source={require('../assets/truck.png')}>2 <View>{/* view content here */}</View>3</BackgroundImage>Modals
RN comes with a handy modal component which allows us to render modals, Modals are controlled using a visible prop. A modal in use could look something like this:
screens/About.tsx
1import React, { useCallback, useState } from 'react'2import { StyleSheet, View, Text, Button, Modal } from 'react-native'3import { globalStyles } from '../styles'4
5export default function About() {6 const [isOpen, setIsOpen] = useState<boolean>(false)7
8 const toggleModal = useCallback(() => {9 setIsOpen(!isOpen)10 }, [isOpen])11
12 return (13 <View style={globalStyles.container}>14 <Text>About Page</Text>15 <Button title="Show Modal" onPress={toggleModal} />16 <Modal visible={isOpen} animationType="slide">17 <Text>Hello from the modal</Text>18 <Button title="Close Modal" onPress={toggleModal} />19 </Modal>20 </View>21 )22}