React Native — Tutorial Guide
This tutorial is the summary of my understanding from below Udemy course:
What is React Native?
Defintion 1: React Native is React.JS + React Native to build Real Native mobile apps (Android & iOS)
Definition 2:
React Native allows developers who know React to create native apps.
React Native gives us collection of special “React components” that can be used in our JSX code.
Those components are compiled into Native components of Android and iOS
Also react native exposes native device features like Camera that can be used in JSX .
What is Reactjs?
A JavaScript library for building user interfaces.
Typically used for web development
But there is actually another library React Dom that adds support to Web support.
Create a React Native App?
We start creating a react native app using framework called Expo CLI.
Expo is a production-grade React Native Framework. Expo provides developer tooling that makes developing apps easier, such as file-based routing, a standard library of native modules, and much more.
npx create-expo-app@latest “- -”template blank my-first-react-native-app (Remove quotes when creating and no space between “- -” and template)
To run your project, navigate to the directory and run one of the following npm commands.
- cd my-first-react-native-app
- npm run android
- npm run ios
- npm run web
Assets folder: Contains all the images that we use in our app.
node_modules: All 3rd party packages that are used under hood.
Package.json: Contains all the dependencies that we use in the project.
app.json: We can configure settings and behaviours of our React Native app
app.js: Contains the app code.
How to run the created app ?
We can run the app in Android/ios real device by downloading expogo app from below link:
AutoFormat:
Ctrl+shift+p and type format document then select prettier format
Now open Terminal in your VSCode editor and run the below :
npm startNow it starts to scan a barcode which can be scanned by Scan button provided in expo app (Android device) ,will run the created app that is build locally in your PC on Android/iOS phone.
Note: In ios expo app , the device’s camera to be opened to scan the barcode as Scan button is not provided like in android Expo app .
A Typical React Native code:
In a react native all UI elements are placed in <view> component as below:
export default function App() {
return (
<View style={styles.container}>
<Text>Hello World</Text>
<Button title='Click '/>
</View>
);
}Styling in React native apps:
There is no css support in React native apps similar to react.
There are 2 ways of adding styles:
- Inline styles
- StyleSheetobjects.
Example for styling Text(Inline styling):
export default function App() {
return (
<View style={styles.container}>
<Text style ={{margin:16,borderWidth:2,borderColor:'red',padding:16}}>Hello World</Text>
<Button title='Click '/>
</View>
);
}Example for styling using Stylesheet objects
import { Button, StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text style ={styles.textStyle}>Hello World</Text>
<Button title='Click '/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
textStyle:{margin:16,borderWidth:2,borderColor:'red',padding:16}
}); Height and Width in React Native ?
https://reactnative.dev/docs/height-and-width
Building Layouts in React Native?
When coming to building layouts in react native , FlexBox is important to create layouts.
A component can specify the layout of its children using the Flexbox algorithm. Flexbox is designed to provide a consistent layout on different screen sizes.
You will normally use a combination of flexDirection, alignItems, and justifyContent to achieve the right layout.
In React Native (Flexbox layout), justifyContent and alignItems control alignment — but on different axes.
The key rule is:
✅ justifyContent → aligns along the main axis
✅ alignItems → aligns along the cross axisAnd the main axis depends on
flexDirection.
Step 1 — Understand the Axes
Default in React Native:
flexDirection: 'column'
Main axis → Vertical
Cross axis → HorizontalflexDirection: 'row'
Main axis → Horizontal
Cross axis → VerticaljustifyContent (Main Axis Alignment)
justifyContent: 'flex-start'
justifyContent: 'center'
justifyContent: 'flex-end'
justifyContent: 'space-between'
justifyContent: 'space-around'
justifyContent: 'space-evenly'✅ alignItems (Cross Axis Alignment)
alignItems: 'flex-start'
alignItems: 'center'
alignItems: 'flex-end'
alignItems: 'stretch'✅ Visual Cheat Sheet
flexDirection: column (default)
VERTICAL = main axis
HORIZONTAL = cross axis
justifyContent → vertical movement
alignItems → horizontal movementflexDirection: row
HORIZONTAL = main axis
VERTICAL = cross axis
justifyContent → horizontal movement
alignItems → vertical movementLayouts are typically created with Flexbox.
Example:
export default function App() {
return (
<View style = {styles.container}>
<View style = {styles.textInputContainer}>
<TextInput style = {styles.textInput}placeholder='Enter Goal'></TextInput>
<Button title='Add Goal' ></Button>
</View>
<View style={styles.goalListContainer}>
<Text>List of Items...</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container:{
paddingTop:50,
paddingHorizontal:16,
flex:1
},
textInputContainer:{
flexDirection:'row',
flex:1,
justifyContent:'space-between',
alignItems:'center',
borderBottomWidth:1,
borderBottomColor:'#CCCCCC',
marginBottom:24
},
textInput:{
width:'70%',
borderWidth:2,
borderColor:'#CCCCCCC',
margin:8,
padding:8
},
goalListContainer:{
flex:5
}
});
In the above code flex is set 5 for list container and 1 for textInputContainer. Th
so that textInputContainer occupies 1/6th of space / height of the screen and
goallistcontainer occupies 5/6th of screen height.
Also flex:1 is set to container in order to occupy full screen height available,
then only its children can occupy required flex heights. Otherwise the all the views
in the screen will be wrapped.UseState:
Now my Layout is created, I want to keep track of entered text. We need to use react hook component useState to handle this.
Also , once we keep track of entered text, we need to display as a list. For that we need to create useState to handle lists. Every time we add a new item, the array in useState is updated using spread operator. Best practice is to update using arrow function
Once we handle lists , we need to display the lists.
We also need to set style to list item. I want to set rounded corner to each list item. I created a style to handle this. But the problem that comes is rounded corner appears in Android, not in iOS. so, in order to handled this ,Wrap Text into View. Wrapping in View supports rounded corners in ios as well . Code updated as below for the above functionality.
How does inputText get passed into the arrow function automatically?
TextInput has a prop called:
onChangeText
React Native automatically calls this function every time the text changes and passes the current text value as an argument.
import { useState } from 'react';
import { Button, StyleSheet, Text, TextInput, View } from 'react-native';
export default function App() {
const [enteredText,setEnteredText] = useState("")
const [courseGoals,setCourseGoals] = useState([])
const textInputHandler = (inputText) => {
setEnteredText(inputText)
}
/*
const onAddGoalHandler = () => {
console.log(enteredText)
console.log(courseGoals)
setCourseGoals((currentCourseGoals) => {
return [...currentCourseGoals,enteredText,]
})
console.log(courseGoals)
}*/
function onAddGoalHandler(){
setCourseGoals((currentCourseGoals) => {
return [...currentCourseGoals,enteredText]
})
}
return (
<View style = {styles.container}>
<View style = {styles.textInputContainer}>
<TextInput style = {styles.textInput}
placeholder='Enter Goal'
onChangeText={textInputHandler}></TextInput>
<Button title='Add Goal' onPress={onAddGoalHandler}></Button>
</View>
<View style={styles.goalListContainer}>
{courseGoals.map((goal,index)=> (
<View key={index} style = {styles.listItemContainerStyle}>
<Text
style = {styles.listItemTextStyle}
>{goal}</Text>
</View>))}
</View>
</View>
);
}
const styles = StyleSheet.create({
container:{
paddingTop:50,
paddingHorizontal:16,
flex:1
},
textInputContainer:{
flexDirection:'row',
flex:1,
justifyContent:'space-between',
alignItems:'center',
borderBottomWidth:1,
borderBottomColor:'#CCCCCC',
marginBottom:24
},
textInput:{
width:'70%',
borderWidth:2,
borderColor:'#CCCCCCC',
margin:8,
padding:8
},
goalListContainer:{
flex:5
},
listItemContainerStyle:{
borderRadius:6,
backgroundColor:'purple',
padding:8,
margin:8
},
listItemTextStyle:{
color:'white',
textAlign:'center',
}
});Explanation of below code:
setCourseGoals((currentCourseGoals) => {
return [...currentCourseGoals, enteredText]
})YES — React passes the latest state value into above function automatically.
Making content Scrollable :
Now we have list that is added when clicked on button. As the list grows it goes out of device screen and will not be scrollable.
In order to make it scrollable, we need to add ScrollView from React native and it needs to wrapped in View component so that it takes the flex area provided for the view component.
<View style={styles.goalListContainer}>
<ScrollView>
{courseGoals.map((goal,index)=> (
<View key={index} style = {styles.listItemContainerStyle}>
<Text
style = {styles.listItemTextStyle}
>{goal}</Text>
</View>))}
</ScrollView>
</View>
ScrollView:
ScrollView renders all its child elements when they are not visible which means the items which are not visible out of screen. Those will be visible only when scrolled. Irrespective of visibility of child items on the screen,scrollview renders all items . This will impact performance and will become performance issue when we have long list. Scroll view is preferable for limited amount of child.
Solution for above issue:
Use FlatList when we want to display dynamic scrollable children/items. This will render only elements that are visible on the screen.
All the items that are off the screen will be rendered lazily only when needed that is they will be rendered when scrolled.
Example of using FlatList:
<FlatList
data={courseGoals}
renderItem={(itemReceived) => {
return (
<View style={styles.listItemContainerStyle}>
<Text style={styles.listItemTextStyle}>
{itemReceived.item.text}
</Text>
</View>
);
}}
keyExtractor={(item, index) => item.id}
></FlatList>
In the above code the courseGoals created a list of objects as FlatList
and expects unique Key for each list item.
One way is to add a Key attribute in the object created. But sometimes the
object might be coming from Backend in that case in order to transform the
object for Key attribute, we can use KeyExtractor attribute to map unique item
in the Object.
Full code in below block:Full Code:
import { useState } from "react";
import {
Button,
FlatList,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from "react-native";
export default function App() {
const [enteredText, setEnteredText] = useState("");
const [courseGoals, setCourseGoals] = useState([]);
const textInputHandler = (inputText) => {
setEnteredText(inputText);
};
/*
const onAddGoalHandler = () => {
console.log(enteredText)
console.log(courseGoals)
setCourseGoals((currentCourseGoals) => {
return [...currentCourseGoals,enteredText,]
})
console.log(courseGoals)
}*/
function onAddGoalHandler() {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredText, id: Math.random().toString() },
]);
}
return (
<View style={styles.container}>
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
placeholder="Enter Goal"
onChangeText={textInputHandler}
></TextInput>
<Button title="Add Goal" onPress={onAddGoalHandler}></Button>
</View>
<View style={styles.goalListContainer}>
<FlatList
data={courseGoals}
renderItem={(itemReceived) => {
return (
<View style={styles.listItemContainerStyle}>
<Text style={styles.listItemTextStyle}>
{itemReceived.item.text}
</Text>
</View>
);
}}
keyExtractor={(item, index) => item.id}
></FlatList>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingTop: 50,
paddingHorizontal: 16,
flex: 1,
},
textInputContainer: {
flexDirection: "row",
flex: 1,
justifyContent: "space-between",
alignItems: "center",
borderBottomWidth: 1,
borderBottomColor: "#CCCCCC",
marginBottom: 24,
},
textInput: {
width: "70%",
borderWidth: 2,
borderColor: "#CCCCCCC",
margin: 8,
padding: 8,
},
goalListContainer: {
flex: 5,
},
listItemContainerStyle: {
borderRadius: 6,
backgroundColor: "purple",
padding: 8,
margin: 8,
},
listItemTextStyle: {
color: "white",
textAlign: "center",
},
});Splitting in to components:
The App.js keeps on increasing its size . The best way to make it lighter is to split components used in to separate components.
Example: The major components we use are GoalInput and GoalItem components. Create a new folder with name Components and move them into those . Also move the corresponding styling as well.
Also corresponding handlers as well. The below component is GoalInput component that contains TextInput and Button.
Also we passed useState as well to maintain state of InputText.
About Props:
In React:
Parent owns the state
Child receives data via props
Child communicates back using functions passed as props
In your case:
👉
App.js= Parent
👉GoalInput= Child
This is passing props to a function.
<GoalInput
visible={modalVisible}
onAddGoal={onAddGoalHandler}
cancel={makeModalInVisible}
/>Internally, React does something like:
GoalInput({
visible: modalVisible,
onAddGoal: onAddGoalHandler,
cancel: makeModalInVisible,
});so inside Goal Input , it becomes like below
props = {
visible: true | false,
onAddGoal: function onAddGoalHandler() { ... },
cancel: function makeModalInVisible() { ... }
}import { StyleSheet, TextInput, View, Button } from "react-native";
import { useState } from "react";
function GoalInput(props) {
const [enteredText, setEnteredText] = useState("");
const textInputHandler = (inputText) => {
setEnteredText(inputText);
};
const onAddGoalHandler = () => {
props.onAddGoal(enteredText);
setEnteredText("");
};
return (
<View style={styles.textInputContainer}>
<TextInput
style={styles.textInput}
placeholder="Enter Goal"
onChangeText={textInputHandler}
value={enteredText}
></TextInput>
<Button title="Add Goal" onPress={onAddGoalHandler}></Button>
</View>
);
}
export default GoalInput;
const styles = StyleSheet.create({
textInputContainer: {
flexDirection: "row",
flex: 1,
justifyContent: "space-between",
alignItems: "center",
borderBottomWidth: 1,
borderBottomColor: "#CCCCCC",
marginBottom: 24,
},
textInput: {
width: "70%",
borderWidth: 2,
borderColor: "#CCCCCCC",
margin: 8,
padding: 8,
},
});Props (short for properties) in React Native are used to pass data from a one component to a another component.
In the above the new component takes an argument props . The entered input to be passed to App.js to display the list of goals . So, the app.js while calling this component will pass the function/handler to this component as props.
The GoalInput passes the enter text input to app.js through this props.
That’s what the below code does :
const onAddGoalHandler = () => {
props.onAddGoal(enteredText);
setEnteredText("");
};
<Button title="Add Goal" onPress={onAddGoalHandler}></Button>
When ever button is pressed onAddGoalHandler is called and this
passes entered input through props. That is received in app.js as below:
App.js
function onAddGoalHandler(enteredText) {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredText, id: Math.random().toString() },
]);
}
We pass props from App.js to GoalInput as below:
GoalInput.js will trigger this props when there in change in TextInput.
<GoalInput onAddGoal={onAddGoalHandler}></GoalInput>Similar approach followed to GoalItem component.
import { StyleSheet } from "react-native";
import { View,Text } from "react-native";
function GoalItem(props){
return (
<View style={styles.listItemContainerStyle}>
<Text style={styles.listItemTextStyle}>
{props.text}
</Text>
</View>
);
}
export default GoalItem
const styles = StyleSheet.create({
listItemContainerStyle: {
borderRadius: 6,
backgroundColor: "purple",
padding: 8,
margin: 8,
},
listItemTextStyle: {
color: "white",
textAlign: "center",
},
})Now App.js has become light as below:
import { useState } from "react";
import {
Button,
FlatList,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from "react-native";
import GoalItem from "./Components/GoalItem";
import GoalInput from "./Components/GoalInput";
export default function App() {
const [courseGoals, setCourseGoals] = useState([]);
function onAddGoalHandler(enteredText) {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredText, id: Math.random().toString() },
]);
}
return (
<View style={styles.container}>
<GoalInput onAddGoal={onAddGoalHandler}></GoalInput>
<View style={styles.goalListContainer}>
<FlatList
data={courseGoals}
renderItem={(itemReceived) => {
return <GoalItem text={itemReceived.item.text} />;
}}
keyExtractor={(item, index) => item.id}
></FlatList>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingTop: 50,
paddingHorizontal: 16,
flex: 1,
},
goalListContainer: {
flex: 5,
},
});Pressable:
Suppose we want to delete an item in the list in React Native when an item is pressed.
In order to do that in React Native we need to wrap our item component in something called Pressable
In React Native,
Pressableis a core component used to detect and respond to various types of touch interactions (like press, long press, etc.).
import { Pressable, StyleSheet } from "react-native";
import { View, Text } from "react-native";
function GoalItem(props) {
return (
<Pressable onPress={props.onDelete}>
<View style={styles.listItemContainerStyle}>
<Text style={styles.listItemTextStyle}>{props.text}</Text>
</View>
</Pressable>
);
}
export default GoalItem;
App.js
function onDeleteHandler() {
console.log("DELETE");
}
<GoalItem
text={itemReceived.item.text}
onDelete={onDeleteHandler}
/>In React Native (and React in general), the
bind()function is used to explicitly set the value ofthisinside a class component method.
In this code bind is used to implement delete functionality as below . To delete an item in the list , we need to pass id of the pressed item to delete handler in App.js to delete item . This can be achieved by passing id to GoalItem along with Text as props from App.js .
What Bind does?
Create a new function where
props.onDeletewill be called later,
and when it is called, it will receiveprops.idas its argument.
so this
onPress={props.onDelete.bind(this, props.id)}equal to
onPress={() => props.onDelete(props.id)}App.js:
<GoalItem
text={itemReceived.item.text}
id={itemReceived.item.id}
onDelete={onDeleteHandler}
/>
function onDeleteHandler(id) {
console.log("DELETE");
setCourseGoals((currentCourseGoals) => {
return currentCourseGoals.filter((goal) => goal.id != id);
});
}In React Native (just like in JavaScript), the
filter()function is used on arrays to create a new array containing only elements that match a specific condition.
We update the items to be deleted based on the id passed to delete handler and filter the item that are needed that match Id.
function onDeleteHandler(id) {
console.log("DELETE");
setCourseGoals((currentCourseGoals) => {
return currentCourseGoals.filter((goal) => goal.id != id);
});
}GoalItem.JS
//Receiving id as props and binding id to deletehandler through bind
<Pressable onPress={props.onDelete.bind(this, props.id)}>
<View style={styles.listItemContainerStyle}>
<Text style={styles.listItemTextStyle}>{props.text}</Text>
</View>
</Pressable>We can also achieve delete using other way as below:
const onDeleteGoalHandler = () => {
console.log("OnDelete Goal Handler....");
props.onDelete(props.id);
};
<Pressable onPress={onDeleteGoalHandler}> <View style={styles.listItemContainerStyle}>
<Text style={styles.listItemTextStyle}>{props.text}</Text>
</View>
</Pressable>
Ripple Effect:
When we click on an item and expect some feedback like some ripple effect, which we can achieve in android using android:ripple. Where in iOS we need to set the style separately for Pressable.
<View style={styles.listItemContainerStyle}>
<Pressable
android_ripple={{ color: "#dddddd" }}
onPress={props.onDelete.bind(this, props.id)}
style={({ pressed }) => pressed && styles.pressedItem}
>
<Text style={styles.listItemTextStyle}>{props.text}</Text>
</Pressable>
</View>
//style={({ pressed }) => pressed && styles.pressedItem} ->
//The above line is to support ripple effect for ios
const styles = StyleSheet.create({
pressedItem: {
opacity: 0.5,
},
});Working with Images in React Native:
<Image
style={styles.imageStyle}
source={require("../assets/images/goal.png")}
></Image>
imageStyle: {
width: 100,
height: 100,
margin: 16,
},To display an image placed in the local directory , we need to refer that image using source attribute of Image component. Here source accepts a function with name require that accepts the path of the image.
In the above code the image is stored in images folder created under assets folder.
Also we can set a style to Image.
Modal:
In order to display a modal dialogue , we have a component with name Modal. The Layout that needs to be displayed in Modal can be wrapped in the Modal tag as below:
<Modal visible={props.visible} animationType="slide">
<View style={styles.modalContainer}>
<View style={styles.textInputContainer}>
<Image
style={styles.imageStyle}
source={require("../assets/images/goal.png")}
></Image>
<TextInput
style={styles.textInput}
placeholder="Enter Goal"
onChangeText={textInputHandler}
value={enteredText}
></TextInput>
<View style={styles.buttonContainer}>
<View style={styles.buttonStyle}>
<Button
title="Add Goal"
onPress={onAddGoalHandler}
color="#5e0acc"
></Button>
</View>
<View style={styles.buttonStyle}>
<Button
title="Cancel"
onPress={props.cancel}
color="#f31282"
></Button>
</View>
</View>
</View>
</View>
</Modal>In the above code fragment , the information/layout that displays textinput field with some buttons below it is all wrapped in Modal.
The Modal got two attributes named visible and animationType. The Modal visibility can be controlled by props sent from another screen App.js and AnimationType is set as “slide” which means Modal will be displayed with this animation when it is made visible.git.
Full source code for the Goals app is below:
To Install React Dev tools use below command:
sudo npm install -g react-devtools //sudo is needed when we run this in mac . For Windows sudo is not needed.
To open react dev tools use below command in the terminal:
react-devtools
Create a Custom component :
Here I am trying to create a custom button. How I create is to wrap a Text into View component. The custom component which is button should accept the name of the button through props . But in react we can directly use a property called children that can be directly used.
import { View, Text } from "react-native";
function PrimaryButton({ children }) {
return (
<View>
<Text>{children}</Text>
</View>
);
}
export default PrimaryButton;With props:
import { View, Text } from "react-native";
function PrimaryButton(props) {
return (
<View>
<Text>{props.children}</Text>
</View>
);
}
export default PrimaryButton;What is
childrenin React?In React (and React Native),
childrenis a special prop automatically passed to components that wrap other elements.Why use
childreninstead ofprops.children?The above 2 code snippets are equivalent. But we’re just using object destructuring to extract
childrenfrompropsdirectly.children makes components reusable and more flexible.
What is props?
In React, every component receives exactly one argument:
propsIt is an object that contains all data passed from the parent.
<PrimaryButton color="blue">Click Me</PrimaryButton>React converts this to:
props = {
color: "blue",
children: "Click Me"
}children is just a property inside props.
props.childrenIt represents whatever is written between the opening and closing tags of a component.
<PrimaryButton>
Click Me
</PrimaryButton>
"Click Me" becomes props.childrenVersion 1 — Destructuring
function PrimaryButton({ children }) {
return (
<View>
<Text>{children}</Text>
</View>
);
}Version 2 — Full props object
function PrimaryButton(props) {
return (
<View>
<Text>{props.children}</Text>
</View>
);
}How to build a shadow around a component?
For Android we have an attribute with name elevation . But in iOS this does not work, we need to achieve shadow with 4 different shadow attributes.
Let us assume , I want to style a component for which I need to add shadow. I do as below:
onst styles = StyleSheet.create({
inputcontainerstyle: {
padding: 16,
marginTop: 100,
marginHorizontal: 16,
backgroundColor: "#72063c",
borderRadius: 8,
elevation: 4,
shadowColor: "black",
shadowOffset: { width: 0, height: 2 },
shadowRadius: 6,
shadowOpacity: 0.25,
},
});Explanation:
shadowColor : Color of the shadow (e.g., "black")
shadowOffset : How far the shadow is offset from the element
. width: 0 : No horizontal offset
.height: 2. : Vertical offset of 2px (shadow appears below)
shadowOpacity : Transparency of the shadow (0 = invisible, 1 = solid)
shadowRadius :Blur radius of the shadow (higher = softer shadow edges)
How to add a Linear Gradient ?
Expo provides a package called expo-linear-gradient that allows us to add linear gradient.
Open a new Terminal in VSCode and enter below command:
npx expo install expo-linear-gradient export default function App() {
return (
<LinearGradient
colors={["#ddb52f", "#510428"]}
style={styles.rootElementStyle}
>
<StartGameScreen />
</LinearGradient>
);
}
const styles = StyleSheet.create({
rootElementStyle: {
flex: 1,
},
});LinearGradient takes colors array and also we can set style to that using stylesheet, but we don’t set background as it is set using array of colors to have gradient.
How to set an Image as background in React Native?
ImageBackground is a component that can be used to set image as background.
The attributes of ImageBackGround are similar to image.
resizemode is another attribute of this component that resizes images based on device.
<ImageBackground
source={require("./assets/images/background.png")}
resizeMode="cover"
style={styles.rootElementStyle}
imageStyle={styles.backgroundImageStyle}
>
//Styles
const styles = StyleSheet.create({
rootElementStyle: {
flex: 1,
},
backgroundImageStyle: {
opacity: 0.15,
},
});
//ImageStyle attribute is used to have gradient and image appear.
//If not set only Background image is visible and not gradient.
//style is used to full available size of the screen by setting flex as 1Convert a string to Number:
In ReactJs we use below function that is globally available to convert String to Number ( Provided the entered string is valid number).
function name is parseInt
Function to check whether parsed value is number or not: isNan(parsedNumber)
To display Alert:
Alert.alert(title,message,AlertButton[])
Example:
const parsedNumber = parseInt(enteredNumber)
//Where enteredNumber is of type String
if (isNaN(parsedNumber) || parsedNumber <= 0 || parsedNumber > 99) {
Alert.alert("Invalid Input", "Number should be b/w 1 and 99", [
{ text: "okay", style: "destructive", onPress: resetInputHandler },
]);
}
SafeAreaView:
The purpose of SafeAreaView is to render content within the safe area boundaries of a device. It is currently only applicable to iOS devices with iOS version 11 or later.
SafeAreaViewis a component provided by React Native (specifically fromreact-nativeitself orreact-native-safe-area-context) that helps ensure your UI is not obscured by device-specific elements like:the notch (on iPhones),status bar (Android/iOS),home indicator, or rounded corners.
We need to wrap the root element in the SafeAreaView as below:
<LinearGradient
colors={["#ddb52f", "#510428"]}
style={styles.rootElementStyle}
>
<ImageBackground
source={require("./assets/images/background.png")}
resizeMode="cover"
style={styles.rootElementStyle}
imageStyle={styles.backgroundImageStyle}
>
<SafeAreaView style={styles.rootElementStyle}>{screen}</SafeAreaView>
</ImageBackground>
</LinearGradient>Adding Icons in React Native:
The @expo/vector-icons library is installed by default on the template project using npx create-expo-app and is part of the expo package. It is built on top of react-native-vector-icons and uses a similar API. It includes popular icon sets you can browse at icons.expo.fyi.
import { View, StyleSheet } from 'react-native';
import Ionicons from '@expo/vector-icons/Ionicons';
export default function App() {
return (
<View style={styles.container}>
<Ionicons name="checkmark-circle" size={32} color="green" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});Custom Fonts:
To install custom fonts use below command:
npm install expo-fontLoad custom fonts in app.js:
import { useFonts } from "expo-font";
//To load fonts using useFonts react-hook
const[fontLoading] = useFonts({
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
"open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
});
useFonts returns a boolean in an array here named as fontLoading.
Using that variable we can check whether fonts are loaded or not.
Note:OpenSans-Regular.ttf and OpenSans-Bold.ttf to be copied to
assets folder under fonts folder
In order to create a loading/splash screen while the app loads the fonts use below package:
npm install expo-app-loadingTo import:
import AppLoading from "expo-app-loading";Use below code to show apploading while fonts are loading:
const [fontLoading] = useFonts({
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
"open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
});
if (!fontLoading) {
return <AppLoading />;
}In order to use the loaded custom font in any of the components , we can use fontfamily attribute from stylesheet as below:
import { Text, StyleSheet } from "react-native";
import colors from "../../constants/Colors";
function Title({ children }) {
return <Text style={styles.titleStyle}>{children}</Text>;
}
export default Title;
const styles = StyleSheet.create({
titleStyle: {
fontFamily: "open-sans-bold",
fontSize: 24,
// fontWeight: "bold",
color: colors.titleColor,
textAlign: "center",
borderWidth: 2,
borderColor: colors.titleColor,
padding: 12,
},
});Dimensions API:
Dimensions API provide various attributes to have control on various aspects(Margin,Padding) of Window.
Here Window means the visual content of app excluding status bar.
Where as Screen mean including the status bar.
In iOS Window and screen are same.
Example:
const deviceWidth = Dimensions.get("window").width;
//Based on deviceWidth, I want to set some attributes like
// Margin/Padding or something.
const styles = StyleSheet.create({
container: {
borderWidth: 4,
borderColor: colors.accent500,
padding: deviceWidth < 380 ? 12 : 24,
margin: deviceWidth < 380 ? 12 : 24,
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
},Handing Orientation:
By default the orientation is “Portrait” in app.json.
In order for the screen to adjust according to orientation we need to set it “default” so that when screen orientation is changed the content of the app adjusts automatically.
"expo": {
"name": "my-number-game-app",
"slug": "my-number-game-app",
"version": "1.0.0",
"orientation": "default",Based on device orientation , if the screen needs to be changed, the code that changes due to orientation should be included in component section rather than file section , I mean outside of style function.
useWindowDimensions:
Is a hook to dynamically set adjust sizes for different orientations.
const { width, height } = useWindowDimensions();
const marginTopDistance = height < 380 ? 30 : 100;
//Merge this dynamic style as below:
return (
<View style={[styles.containerStyle, { marginTop: marginTopDistance }]}>Navigation:
The below component to be used to handle navigation ReactNative screens
The below packages to be installed:
npm install @react-navigation/native
dependencies into an Expo managed project:
npx expo install react-native-screens react-native-safe-area-context
App.js to be modifed as below:
const Stack = createNativeStackNavigator();
export default function App() {
return (
<>
<StatusBar style="dark"></StatusBar>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="MealsCategories" component={CategoryScreen} />
<Stack.Screen name="MealsOverview" component={MealsOverviewScreen} />
</Stack.Navigator>
</NavigationContainer>
</>
);
}Documentation with example on Navigation bewteen screens:
We will have a prop available to navigate between screens and that is used to navigate:
navigation.navigate("MealsOverview");
//Whole code
function CategoryScreen({ navigation }) {
function RenderCategoryItem(itemData) {
function PressHandler() {
navigation.navigate("MealsOverview");
}
return (
<CategoryGridTile
title={itemData.item.title}
color={itemData.item.color}
onPress={PressHandler}
></CategoryGridTile>
);
}
return (
<FlatList
data={CATEGORIES}
keyExtractor={(item) => item.id}
renderItem={RenderCategoryItem}
numColumns={2}
></FlatList>
);
}Setting up Default Screen:
When setting up a Navigator (like <Stack.Navigator>) and registering its screens (via <Stack.Screen>), you can decide which screen will be shown as a default when the app starts.
Out of the box, the top-most screen (i.e. the first child inside of <Stack.Navigator>) is used as the initial screen.
I.e., in the following example, the AllProducts screen would be shown as an initial screen when the app starts:
<Stack.Navigator>
<Stack.Screen name="AllProducts" component={AllProducts} /> // initial screen
<Stack.Screen name="ProductDetails" component={ProductDetails} />
</Stack.Navigator>You can therefore change the initial screen by changing the <Stack.Screen> order. Alternatively, there also is an initialRouteName prop that can be set on the navigator component (i.e., on <Stack.Navigator> in this case):
<Stack.Navigator initialRouteName="ProductDetails">
<Stack.Screen name="AllProducts" component={AllProducts} />
<Stack.Screen name="ProductDetails" component={ProductDetails} /> // initial screen
</Stack.Navigator>How to pass Parameters from One screen to another screen?
https://reactnavigation.org/docs/params
navigation.navigate("MealsOverview", {
categoryId: itemData.item.id,
});
Where categoryId is key . It Can be of any name.
We receive the passed value in the screen that is targetted to receive as below. By default route is received as parameter for the screens that are part of Navigation Screen in App.js
function MealsOverviewScreen({ route }) {
const categoryId = route.params.categoryId;Different ways to Navigate to and for:?
(1) back():
Suppose I have 2 screens (list and addscreen) . The first screen is add and with action the flow navigates to addscreen from list. once the required operations in addscreen are done we need to navigate to listscreen.
Simple Definition:
Go back one step in the stack
navigation.goBack();- Pops the current screen off the navigation stack
- Returns to the previous screen in history.
It does NOT:
- choose a screen by name
- reset navigation
- clear history
- guarantee which screen you land on.
replace():
Replace the current screen in the navigation stack with another screen
- Current screen is removed
- New screen is inserted at the same position
- ❌ No back navigation to the replaced screen
reset():
navigation.reset()is one of the most powerful (and misunderstood) APIs in React Navigation. I’ll explain it slowly, visually, and with your exact use-case in mind.“Forget everything the user did before. Start fresh from here.”
👉 Completely wipes the current navigation stack
👉 Creates a brand-new stack from scratch
👉 Sets which screen is active
In short:
navigate():
[A] → [B]
navigate(C)
[A] → [B] → [C]
replace():
[A] → [B]
replace(C)
[A] → [C]
reset():
[A] → [B] → [C]
reset([{ name: A }])
[A]
goBack():
[A] → [B]
goBack()
[A]
useLayoutEffect
Is a hook lets you run side effects synchronously after React has done all DOM (or native view) mutations but before the screen is painted to the user.
In other words:
useEffect → runs after the screen is painted.
useLayoutEffect → runs before the screen is painted.
So it’s used when you need to measure layouts, update UI, or set options that must happen before the user sees the screen.
Scenario in my app :
I want to dynamically set the title of the screen when I navigate to detail screen . In that flow I used this as below as in that screen.
function MealsOverviewScreen({ route, navigation }) {
const categoryId = route.params.categoryId;
const displayMeals = MEALS.filter((mealItem) => {
return mealItem.categoryIds.indexOf(categoryId) >= 0;
});
useLayoutEffect(() => {
const categoryTitle = CATEGORIES.find(
(category) => category.id === categoryId
).title;
navigation.setOptions({
title: categoryTitle,
});
}, [categoryId, navigation]);useCallback:
Its a react hook that prevents the function wrapped in it to be not created repeatedly.
const savePickedLocationHandler = useCallback(() => {
if (!selectedLocation) {
Alert.alert(
"No Location Picked",
"You have to pick location by tapping on map",
);
return;
}
navigation.navigate("AddPlace", {
pickedLatitude: selectedLocation.lat,
pickedLongitude: selectedLocation.lang,
});
}, [navigation, selectedLocation]);
Note: The above function will be recreated only when navigation and selectedLocation
value changes .This improves performance.How to style Status bar dynamically when using ReactNavigation?
Get Csmworks’s stories in your inbox
Join Medium for free to get updates from this writer.
To set the title from App.js:
<Stack.Screen
name="MealsCategories"
component={CategoryScreen}
options={{
title: "Categories",
}}
/>Suppose all the screens should have same style for Header (title and bg ) and some back ground for content of the screen. Instead of setting individually react Native provides an option to set globally using screenOptions as below.
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: "#351401",
},
headerTintColor: "white",
contentStyle: { backgroundColor: "#3f2f25" },
}}
>All the screens that are part of createNativeStackNavigator by default receive navigate and route as parameters as below.
function MealsOverviewScreen({ route, navigation }) {
const categoryId = route.params.categoryId;
const displayMeals = MEALS.filter((mealItem) => {
return mealItem.categoryIds.indexOf(categoryId) >= 0;
});
useLayoutEffect(() => {
const categoryTitle = CATEGORIES.find(
(category) => category.id === categoryId
).title;
navigation.setOptions({
title: categoryTitle,
});
}, [categoryId, navigation]);How to add a button to Header dynamically ?
function MealDetailScreen({ route, navigation }) {
const mealId = route.params.mealId;
const SelectedMeal = MEALS.find((meal) => meal.id === mealId);
function headerButtonPressHandler() {
console.log("Header Button Pressed");
}
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => {
return (
<Button title="Tap Me" onPress={headerButtonPressHandler}></Button>
);
},
});
});Custom IconButton:
Suppose I want to add custom Icon button . I want to create a reusable component . It can be done as below :
import { Pressable, StyleSheet } from "react-native";
import { Ionicons } from "@expo/vector-icons";
function IconButton({ icon, color, onPress }) {
return (
<Pressable
onPress={onPress}
style={({ pressed }) => pressed && style.pressed}
>
<Ionicons name={icon} size={24} color={color}></Ionicons>
</Pressable>
);
}
export default IconButton;
const style = StyleSheet.create({
pressed: {
opacity: 0.7,
},
});The below code is added to have visual feedback when Icon is pressed.
For Pressable we already will receive a parameter pressed . Only when Icon is pressed style will be applied which will set opacity as set in style.
style={({ pressed }) => pressed && style.pressed}Navigation Drawer:
https://reactnavigation.org/docs/drawer-navigator/
Steps:
import { createDrawerNavigator } from “@react-navigation/drawer”;
const Drawer = createDrawerNavigator();
export default function App() {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="Welcome"
screenOptions={{
headerStyle: { backgroundColor: "#6a57d5" },
headerTintColor: "white",
drawerActiveBackgroundColor: "#c3baec",
drawerActiveTintColor: "#6a57d5",
}}
>
<Drawer.Screen
name="Welcome"
component={WelcomeScreen}
options={{
drawerLabel: "Welcome Screen",
drawerIcon: ({ color, size }) => (
<Ionicons name="home" color={color} size={size}></Ionicons>
),
}}
/>
<Drawer.Screen
name="Settings"
component={Screen2}
options={{
drawerIcon: ({ color, size }) => (
<Ionicons name="settings" color={color} size={size}></Ionicons>
),
}}
/>
</Drawer.Navigator>
</NavigationContainer>
);
} BottomTabs:
https://reactnavigation.org/docs/bottom-tab-navigator/
npm install @react-navigation/bottom-tabs
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator
initialRouteName="Welcome"
screenOptions={{
headerStyle: { backgroundColor: "#6a57d5" },
headerTintColor: "white",
tabBarActiveTintColor: "#6a57d5",
}}
>
<Tab.Screen
name="Welcome"
component={WelcomeScreen}
options={{
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" color={color} size={size}></Ionicons>
),
}}
/>
<Tab.Screen
name="Settings"
component={Screen2}
options={{
tabBarIcon: ({ color, size }) => (
<Ionicons name="settings" color={color} size={size}></Ionicons>
),
}}
/>
</Tab.Navigator>
</NavigationContainer>
);
}Imports:
import { Ionicons } from "@expo/vector-icons";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { createDrawerNavigator } from "@react-navigation/drawer";
import { NavigationContainer } from "@react-navigation/native";
import "react-native-gesture-handler";
import Screen2 from "./screens/Screen2";
import WelcomeScreen from "./screens/WelcomeScreen";package.json:
{
"name": "mydrawerapp",
"main": "index.js",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"lint": "expo lint"
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-navigation/bottom-tabs": "^7.8.5",
"@react-navigation/drawer": "^7.7.3",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.20",
"expo": "~54.0.23",
"expo-constants": "~18.0.10",
"expo-font": "~14.0.9",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.10",
"expo-linking": "~8.0.8",
"expo-router": "~6.0.14",
"expo-splash-screen": "~31.0.10",
"expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.8",
"expo-web-browser": "~15.0.9",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@types/react": "~19.1.0",
"eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0",
"typescript": "~5.9.2"
},
"private": true
}index.js:
import { registerRootComponent } from "expo";
import App from "./App";
registerRootComponent(App);babel.config.js:
module.exports = {
presets: ["babel-preset-expo"],
plugins: ["react-native-reanimated/plugin"],
};Nested Navigation:
Suppose I want to add Navigation Drawer in the stack Navigator. It can be done as below:
App.js
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator
screenOptions={{
headerStyle: {
backgroundColor: "#351401",
},
headerTintColor: "white",
sceneStyle: { backgroundColor: "#3f2f25" },
drawerContentStyle: { backgroundColor: "#351401" },
drawerInactiveTintColor: "white",
drawerActiveTintColor: "#351401",
drawerActiveBackgroundColor: "#cc6c35",
}}
>
<Drawer.Screen
name="Categories"
component={CategoryScreen}
options={{
title: "All Categories",
drawerIcon: ({ color, size }) => (
<Ionicons color={color} size={size} name="list"></Ionicons>
),
}}
></Drawer.Screen>
<Drawer.Screen
name="favorites"
component={FavoriteScreen}
options={{
title: "Favorities screen",
drawerIcon: ({ color, size }) => (
<Ionicons color={color} size={size} name="heart"></Ionicons>
),
}}
></Drawer.Screen>
</Drawer.Navigator>
);
}
export default function App() {
return (
<>
<SafeAreaProvider>
<StatusBar style="light"></StatusBar>
<SafeAreaView style={{ flex: 1 }} edges={["top"]}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: "#351401",
},
headerTintColor: "white",
contentStyle: { backgroundColor: "#3f2f25" },
}}
>
<Stack.Screen
name="MealsCategories"
component={DrawerNavigator}
options={{
headerShown: false,
}}
/>
<Stack.Screen
name="MealsOverview"
component={MealsOverviewScreen}
options={{ title: "Meals Overview" }}
/>
<Stack.Screen
name="Meals Detail"
component={MealDetailScreen}
options={{ title: "Meals Detail" }}
/>
</Stack.Navigator>
</NavigationContainer>
</SafeAreaView>
</SafeAreaProvider>
</>
);
}Context API :
The Context API in React Native allows for global state management.
CreateContext:
createContext lets you create a context that components can provide or read.
How to create a context?
const SomeContext = createContext(defaultValue)
The above code will Create the context with a default value (optional)
Example:
Suppose I want to create a context to be used in my components with different values. So, initialise contexts in createContext() as below.
import { createContext} from "react";
export const FavoriteContext = createContext({
ids: [],
addFavorite: (id) => {}, //Dummy definition of a function //Actual implementation to be done later
removeFavorite: (id) => {},
});
//We use export front of createContext() so that it can be used in other compponents laterThe above example creates a context with an array and two functions.
Provide the context:
How to provide the context to be used by other components?
function FavoriteContextProvider({ children }) {
const [favoriteIds, setFavoriteIds] = useState([]);
function addFavorite(id) {
setFavoriteIds((currentId) => [...currentId, id]);
}
function removeFavorite(id) {
setFavoriteIds((currentId) => currentId.filter((mealId) => mealId !== id));
}
const value = {
ids: favoriteIds,
addFavorite: addFavorite,
removeFavorite: removeFavorite,
};
return (
<FavoriteContext.Provider value={value}>
{children}
</FavoriteContext.Provider>
);
}
export default FavoriteContextProvider;Purpose of Below code:
const value = {
ids: favoriteIds,
addFavorite: addFavorite,
removeFavorite: removeFavorite,
};We need to pass all the values of context which we do by declaring a property with name value . In that property ids (key)refer to the id declared while creating context and its value represent the favoriteIds created as part of useState.
Similarly we have addFavorite key and its corresponding implemented function and removeFavorite key with its correspoding function implementation .
The Provider component accepts a value prop,which is the data to be shared.
This created value constant to be supplied to value property of provider as below
<FavoriteContext.Provider value={value}>
{children}
</FavoriteContext.Provider>Full code of Context:
import { createContext, useState } from "react";
export const FavoriteContext = createContext({
ids: [],
addFavorite: (id) => {},
removeFavorite: (id) => {},
});
function FavoriteContextProvider({ children }) {
const [favoriteIds, setFavoriteIds] = useState([]);
function addFavorite(id) {
setFavoriteIds((currentId) => [...currentId, id]);
}
function removeFavorite(id) {
setFavoriteIds((currentId) => currentId.filter((mealId) => mealId !== id));
}
const value = {
ids: favoriteIds,
addFavorite: addFavorite,
removeFavorite: removeFavorite,
};
return (
<FavoriteContext.Provider value={value}>
{children}
</FavoriteContext.Provider>
);
}
export default FavoriteContextProvider;wrap your root component (App.js or any component that needs the context) with this Provider.
export default function App() {
return (
<>
<FavoriteContextProvider>
<NavigationContainer>
------- some code------
</NavigationContainer>
</FavoriteContextProvider>
</>
);
}
Consume the context:
use the useContext hook in any child component to access the shared data.
CodeSample:
const favoriteMealsContext = useContext(FavoriteContext);
const mealId = route.params.mealId;
const isMealFavorite = favoriteMealsContext.ids.includes(mealId);
function favoriteChangeHandler() {
console.log("Header Button Pressed");
if (isMealFavorite) {
favoriteMealsContext.removeFavorite(mealId);
} else {
favoriteMealsContext.addFavorite(mealId);
}
}Redux:
We can achieve the same state management that is achieved using contextAPI with Redux as well.
Redux Toolkit is popularly used toolkit for this . Below is the link:
The first we need to do is install Redux toolkit as below
npm install @reduxjs/toolkit
npm install react-reduxReducers:
In Redux, reducers are pure functions that take the current state of the application and an action as arguments, and return a new state. They are the sole mechanism for updating the application’s state in a Redux store.
characteristics and rules of Redux reducers:
Pure Functions:
Reducers must be pure functions.They should only calculate the next state based on the state and action arguments.
They should not modify the existing state directly; instead, they must make immutable updates by creating copies of the necessary parts of the state and modifying those copies.
They should not have any side effects, such as making API calls, performing asynchronous operations, or generating random values.
Signature:
A reducer function always has the signature (state, action) => newState.
state: Represents the current state of the application or a specific slice of the state.action: An object describing what happened, typically containing atypeproperty and optionally apayloadwith data.newState: The new state object returned after processing the action.
Create a .js file with some name like store.js and include below import
import {configureStore} from '@reduxjs/toolkit'Use Provider to include redux provider in the components to use as below (in app.js)
import { Provider } from "react-redux";
<Provider store={store}>
// There is a property called store for Provider
// to which we pass the 'store.js' that is created under redux folder of our project.
</Provider>Redux slides:
createSlice:This utility simplifies defining reducers and actions by allowing you to create a “slice” of your Redux state. It automatically generates action creators and action types based on the reducer functions you provide.
In short :Define your state slices using
createSlice.
import { createSlice } from "@reduxjs/toolkit";
const favoritesSlice = createSlice({
name: "favorites",
initialState: {
ids: [],
},
reducers: {
addFavorite: (state, action) => {
state.ids.push(action.payload.id);
},
removeFavorite: (state, action) => {
state.ids.splice(state.ids.indexOf(action.payload.id), 1);
},
},
});
export const addFavorite = favoritesSlice.actions.addFavorite;
export const removeFavorite = favoritesSlice.actions.removeFavorite;
export default favoritesSlice.reducer;Connect to React Native Components:
Use Provider from react-redux to make the store available to your components, and useSelector and useDispatch hooks to interact with the store within your components.
App.js
<Provider store={store}>
<NavigationContainer>
/** Some code **/
</Provider> const favoriteMealIds = useSelector((state) => state.favoriteMealIds);
const dispatch = useDispatch();
function favoriteChangeHandler() {
console.log("Header Button Pressed");
if (isMealFavorite) {
dispatch(removeFavorite({ id: mealId }));
// favoriteMealsContext.removeFavorite(mealId);
} else {
// favoriteMealsContext.addFavorite(mealId);
dispatch(addFavorite({ id: mealId }));
}
}How to close/Navigateback from a screen ?
Suppose I have 2 screens , I navigate from first screen to second screen.
In second screen if I want to navigate back to first screen or close the second screen ,the below is the method from navigate we use to do it.
navigation.goBack();
Where navigate is received as below:
function ManageExpense({ route, navigation }) {
} Create Custom Input Field:
<TextInput {...textInputConfig} />
Where textInputConfig is TextInput props to be send from calling component
instead of providing all properties separately.
function Input({ label, textInputConfig }) {
return (
<View>
<Text>{label}</Text>
<TextInput {...textInputConfig} />
</View>
);
}
export default Input;Handling userinput in a generic way?
Suppose my screen got 3 user inputs (Textfields) and if I want to capture/process the entered values of each input when action is performed like button click.
The traditional way is to maintain useState() for each of the input. If there are so many input elements then number of useState() to maintain for each will also increase . Its better to have generic handling of userInput.
const [inputValues, setInputValues] = useState({
amount: "",
date: "",
description: "",
});
function inputChangeHandler(inputIdentifier, enteredValue) {
setInputValues((curInputValues) => {
return {
...curInputValues,
[inputIdentifier]: enteredValue,
};
});
return (
<View style={styles.form}>
<Text style={styles.title}>Your Expense</Text>
<View style={styles.amountDateContainer}>
<Input
style={styles.rowInput}
label="Amount"
textInputConfig={{
keyboardType: "decimal-pad",
onChangeText: inputChangeHandler.bind(this, "amount"),
value: inputValues.amount,
}}
></Input>
<Input
style={styles.rowInput}
label="Date"
textInputConfig={{
placeholder: "YYYY-MM-DD",
maxLength: 10,
onChangeText: inputChangeHandler.bind(this, "date"),
value: inputValues.date,
}}
></Input>
</View>
<Input
label="Description"
textInputConfig={{
multiline: true,
// autoCapilize: 'none' // Default is sentence
// autoCorrect: false //Default true
onChangeText: inputChangeHandler.bind(this, "description"),
value: inputValues.description,
}}
></Input>
</View>
);Sending Http Requests:
Axios can be used both in Browser based app / and also React native .
npm install axiosSample code to handle Webservices:
import axios from "axios";
const BACKEND_URL =
"https://react-native-expense-app-default-rtdb.firebaseio.com";
export async function storeExpense(expenseData) {
const response = await axios.post(
BACKEND_URL + "/expenses.json",
expenseData
);
const id = response.data.name; //Where nme is the Id auto generated by FireBase
return id;
}
export async function fetchExpenses() {
const response = await axios.get(BACKEND_URL + "/expenses.json");
const expenses = [];
console.log("Received Response is " + response.data);
for (const key in response.data) {
const expenseObj = {
id: key,
amount: response.data[key].amount,
date: new Date(response.data[key].date),
description: response.data[key].description,
};
expenses.push(expenseObj);
}
return expenses;
}
export function updateExpense(id, expenseData) {
return axios.put(BACKEND_URL + `/expenses/${id}.json`, expenseData);
/* const response = await axios.put(
BACKEND_URL + `/expenses/${id}.json`,
expenseData
);
const id = response.data.name;r
return id; */
}
//await updateExpense(editedExpenseId, expenseData); // await not mandatory
export function deleteExpense(id) {
axios.delete(BACKEND_URL + `/expenses/${id}.json`);
}
//await deleteExpense(editedExpenseId);Explanation:
POST:
export async function storeExpense(expenseData) {
const response = await axios.post(
BACKEND_URL + "/expenses.json",
expenseData
);
const id = response.data.name; //Where nme is the Id auto generated by FireBase
return id;
}
Note:
Use async keyword and await -> to wait till response is received.
Since Firebase Realtime Data base is used as Backend , firebase generates ID
automatically which we can retrieve using response.data.name
Call like below:
const id = await storeExpense(expenseData);
GET:
export async function fetchExpenses() {
const response = await axios.get(BACKEND_URL + "/expenses.json");
const expenses = [];
console.log("Received Response is " + response.data);
for (const key in response.data) {
const expenseObj = {
id: key,
amount: response.data[key].amount,
date: new Date(response.data[key].date),
description: response.data[key].description,
};
expenses.push(expenseObj);
}
return expenses;
}
Note:
In the above code once response is received we parse the response in forloop
and convert to the data object as required and push to the list.
Also date is converted to Date() to format data accordingly.
How to call ?
const expenses = await fetchExpenses();For Update and delete we are not expecting anything from response due to which those functions are not declared as async. If needed can be changed accordingly.
ErrorHandling:
const [error, setError] = useState();
try {
await deleteExpense(editedExpenseId);
expenseCtx.deleteExpense(editedExpenseId);
navigation.goBack();
} catch (error) {
setError("Error Deleting");
}
function errorHandler() {
setError(null);
}
if (error && !isFetchingState) {
return <ErrorOverlay message={error} onConfirm={errorHandler} />;
}
import { Button, StyleSheet, Text, View } from "react-native";
import { GlobalStyles } from "../../constants/styles";
function ErrorOverlay({ message, onConfirm }) {
return (
<View style={styles.container}>
<Text style={[styles.text, styles.title]}>An error occured!</Text>
<Text style={styles.text}>{message}</Text>
<Button title="Okay" onPress={onConfirm}></Button>
</View>
);
}
export default ErrorOverlay;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 24,
backgroundColor: GlobalStyles.colors.primary700,
},
text: {
color: GlobalStyles.colors.white,
textAlign: "center",
marginBottom: 8,
},
title: {
fontSize: 20,
fontWeight: "bold",
},
});About useNavigation() hook:
If a component is not screen component we will not receive navigate as props. In that case we use useNavigation() so that we can navigate to required screen by any of following methods:
navigation.navigate("Signup"). where Signup is target screen to be navigated to The above code displays a back arrow on the top of the Signup screen . If we do not want to see that we can use below method which replaces exisitng screen with new screen (Signup) .
navigation.replace("Signup");How to store data in device in React Native ?
In order store data in device , we need to use below package :
Async Storage is asynchronous, unencrypted, persistent, key-value storage solution.
How to prolong splash screen ?
There will be a situation where we want prolong splash screen till we login. Generally this will happen when we want to check whether user is authenticated or not during app load. If authenticated navigate to one screen or else navigate to login to authenticate. In such situations better to use expo’s app loading to prolong during intial load.
expo install expo-app-loadingExample:
function Root() {
const [isTryLogging, setTryLogging] = useState(true);
const authCtx = useContext(AuthContext);
useEffect(() => {
async function fetchToken() {
const token = await AsyncStorage.getItem("token");
if (token) {
authCtx.authenticate(token);
}
setTryLogging(false);
}
fetchToken();
}, []);
if (isTryLogging) {
<AppLoading />;
}
return <Navigation />;A sample authentication utility file to handle use authentication using firebase:
import axios from "axios";
const API_KEY = "AIzaSyBjq_fw5beTSDTE0Hg8NyWLk-N7O-B9t";
export async function createUser(email, password) {
return authenticateUser("signUp", email, password);
}
export function loginUser(email, password) {
return authenticateUser("signInWithPassword", email, password);
}
async function authenticateUser(mode, email, password) {
const url = `https://identitytoolkit.googleapis.com/v1/accounts:${mode}?key=${API_KEY}`;
const response = await axios.post(url, {
email: email,
password: password,
returnSecureToken: true,
});
// console.log(response.data);
const token = response.data.idToken;
return token;
}https://github.com/chandragithub2014/my-react-native-authentication-sample-firbase
How to add style to a Pressable component ?
Suppose I have a requirement where I need to create a reusable IconButton that is pressable. In that case if I want to apply style , we need to apply two styles : one is default style and other is the style that is applied when compoent is pressed. In such scenario we need to create a style as function and check if component is pressed. In this scenario we need to add styles in an array as below:
import { Pressable, StyleSheet } from "react-native";
import { Ionicons } from "@expo/vector-icons";
function CustomIconButton({ icon, size, color, onPress }) {
return (
<Pressable
style={({ pressed }) => [styles.button, pressed && styles.pressed]}
onPress={onPress}
>
<Ionicons name={icon} size={size} color={color} />
</Pressable>
);
}
export default CustomIconButton;
const styles = StyleSheet.create({
button: {
padding: 8,
margin: 4,
justifyContent: "center",
alignItems: "center",
},
pressed: {
opacity: 0.7,
},
});Using Native Device Features:
How to access camera to capture image and display ?
Introduction:
expo-camera provides a React component that renders a preview of the device's front or back camera. The camera's parameters such as zoom, torch, and flash mode are adjustable. Using CameraView, you can take photos and record videos that are saved to the app's cache. The component is also capable of detecting bar codes appearing in the preview.
Installation:
npx expo install expo-cameraHow to configure?
We need to configure plugin in App.json
"plugins": [
[
"expo-image-picker",
{
"cameraPermission": "The app accesses your camera os to let you capture photos of places."
}
]The below is the component that is implemented to get the camera permissions and proceed.
import { Alert, Image, StyleSheet, Text, View } from "react-native";
import {
launchCameraAsync,
useCameraPermissions,
PermissionStatus,
} from "expo-image-picker";
import { useState } from "react";
import { Colors } from "../../constants/colors";
import OutlinedButton from "../UI/OutlinedButton";
function ImagePicker() {
const [pickedImage, setPickedImage] = useState();
const [permission, requestPermission] = useCameraPermissions();
async function verifyPermissions() {
if (permission.status === PermissionStatus.UNDETERMINED) {
const permissionResponse = await requestPermission();
return permissionResponse.granted;
}
if (permission.status === PermissionStatus.DENIED) {
Alert.alert(
"Insufficient Permissions",
"You need to grant permissions of camera to use"
);
return false;
}
return true;
}
async function takeImageHandler() {
const isPermissionsGranted = await verifyPermissions();
if (!isPermissionsGranted) {
return;
}
const image = await launchCameraAsync({
allowsEditing: true,
aspect: [16, 9],
quality: 0.5,
});
setPickedImage(image.assets[0].uri);
// console.log(image);
}
let imagePreview = <Text>No Image Taken Yet...</Text>;
if (pickedImage) {
imagePreview = (
<Image style={styles.image} source={{ uri: pickedImage }}></Image>
);
}
return (
<View>
<View style={styles.imagePreview}>{imagePreview}</View>
<OutlinedButton icon="camera" onPress={takeImageHandler}>
Take Image
</OutlinedButton>
</View>
);
}
export default ImagePicker;
const styles = StyleSheet.create({
imagePreview: {
width: "100%",
height: 200,
marginVertical: 8,
justifyContent: "center",
alignItems: "center",
backgroundColor: Colors.primary100,
borderRadius: 4,
},
image: {
width: "100%",
height: "100%",
},
});useCameraPermissions:
This hook is used to check camera permissions. Whether user using the app granted permissions or not can be know by this camera permissions.
launchCameraAsync opens the device camera, lets the user take a photo, and returns the image data to your React Native app. It has some options to edit ,crop and other properties .
The image returned by the above function can be accessed by the uri property as below:
const image = await launchCameraAsync({
allowsEditing: true,
aspect: [16, 9],
quality: 0.5,
});
image.assets[0].uriLocation ?
How to fetch current location of user?
Search for “expo location “
npx expo install expo-locationThe below are the imports needed for it :
import {
getCurrentPositionAsync,
useForegroundPermissions,
PermissionStatus,
LocationAccuracy,
} from "expo-location";
We need to handle permission to access location in the app as below:
For that we use useForegroundPermissions hook.
const [locationPermissionInformation, requestPermission] =
useForegroundPermissions();
async function verifyPermissions() {
if (
locationPermissionInformation.status === PermissionStatus.UNDETERMINED
) {
const permissionResponse = await requestPermission();
return permissionResponse.granted;
}
if (locationPermissionInformation.status === PermissionStatus.DENIED) {
Alert.alert(
"Insufficient Permissions",
"You need to grant permissions of location to use"
);
return false;
}
return true;
}Once permission is checked we receive the location as below:
const location = await getCurrentPositionAsync({
accuracy: LocationAccuracy.High,
timeout: 5000,
});Note: In iPhone simulator once permission is granted , we can receive the current location . But on Android emulator it is not received , it can only be rceived by setting some location in emulator. Otherwise better to check in real device.
Sample Example:
async function getLocationHandler() {
const isPermissionsGranted = await verifyPermissions();
if (!isPermissionsGranted) {
return;
}
const location = await getCurrentPositionAsync({
accuracy: LocationAccuracy.High,
timeout: 5000,
});
console.log("location is ");
console.log(location);
console.log(location.coords.latitude);
console.log(location.coords.longitude);
}To show maps preview , the below api is used?
https://developers.google.com/maps/documentation/maps-static/overview
But the above API needs gmail account and Credit card details.
The better is to use expo mapview
https://docs.expo.dev/versions/latest/sdk/map-view/
npx expo install react-native-mapsThe below is the samplecode to use it:
import MapView, { Marker } from "react-native-maps";
export function MapPreview({ lat, lang }) {
return (
<MapView
style={{ width: "100%", height: "100%" }}
initialRegion={{
latitude: lat,
longitude: lang,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
scrollEnabled={false}
zoomEnabled={false}
pitchEnabled={false}
rotateEnabled={false}
>
<Marker
coordinate={{
latitude: lat,
longitude: lang,
}}
/>
</MapView>
);
}
Note:Latitude ,longitude define center of the map
delta defines how much content is visible beside it. Basically Zoom levelUsing SQLite DB in ReactNative:
Use Expo Sqlite for handling Database in React Native
npx expo install expo-sqliteI created database util to handle basic Database operations:
import * as SQLite from "expo-sqlite";
import { Place } from "../models/place";
let db;
export async function init() {
try {
db = await SQLite.openDatabaseAsync("favoriteplaces.db");
await db.execAsync(`
CREATE TABLE IF NOT EXISTS places (
id INTEGER PRIMARY KEY NOT NULL,
title TEXT NOT NULL,
imageUri TEXT NOT NULL,
address TEXT NOT NULL,
lat REAL NOT NULL,
lng REAL NOT NULL
);
`);
return true; // ✅ success
} catch (error) {
console.error("DB init error:", error);
throw error; // ❌ failure
}
}
export function getDatabase() {
return db;
}
export async function insertPlace(place) {
try {
const db = await getDatabase();
const result = await db.runAsync(
`INSERT INTO places (title, imageUri, address, lat, lng)
VALUES (?, ?, ?, ?, ?)`,
[
place.title,
place.imageUri,
place.addresss,
place.location.lat,
place.location.lang,
],
);
console.log("✅ Insert success", result);
return result.lastInsertRowId;
} catch (error) {
console.log("❌ Insert failed", error);
throw error;
}
}
export async function fetchPlaces() {
try {
const db = await getDatabase();
const result = await db.getAllAsync("SELECT * FROM places");
return result.map(
(row) =>
new Place(
row.title,
row.imageUri,
{
addresss: row.address,
lat: row.lat,
lang: row.lng,
},
row.id,
),
);
//console.log("In Fetch Places");
//console.log(result);
} catch (error) {
console.log("Fetch places failed", error);
throw error;
}
}
export async function fetchPlaceDetails(id) {
console.log("In fetchPlaceDetails");
console.log(id);
try {
const db = await getDatabase();
const result = await db.getAllAsync("SELECT * FROM places WHERE id = ?", [
id,
]);
if (result.length === 0) {
return null; // or throw error if you prefer
}
const row = result[0];
return new Place(
row.title,
row.imageUri,
{
addresss: row.address,
lat: row.lat,
lang: row.lng,
},
row.id,
);
} catch (error) {
console.log("Fetch place by id failed", error);
throw error;
}
}init():
This function initializes the Database by creating required tables. In my app I require only one Table . So , one table is created . The created database is stored in db variable.
That db variable can be accessed in the functions that perform database operations(insert,select,update,delete)
In order to fetch the created db another function is created for that.
export async function init() { } // To initialize db
export function getDatabase() {
return db;
} // To get the created db in various db operations.where should be initialize db ?
We initialize the db by calling init() method. That init() db to be called at start of the app which is app.js as below:
export default function App() {
const [dbinitialized, setDBInitialized] = useState(false);
useEffect(() => {
async function setupDb() {
try {
await init();
setDBInitialized(true);
console.log("Database initialized");
} catch (err) {
console.log("DB init failed", err);
}
}
setupDb();
}, []);
if (!dbinitialized) {
return <AppLoading />;
}To call other operations like insert in the required screens we do as below:
import { insertPlace } from "../util/Database";
async function createPlaceHandler(place) {
try {
const id = await insertPlace(place);
console.log("Inserted with ID:", id);
} catch (err) {
console.log(err);
Alert.alert("Error", "Could not save place");
}
}Note: Not to forget import statement.
Expo Bare Workflow:
The Expo Bare workflow is a hybrid approach for React Native, giving you a standard react-native-cli project with native folders (ios, android) but pre-configured with Expo tools like expo-modules (Unimodules) for easy access to many Expo SDK features (OTA updates, notifications). It offers full native code control for custom modules and deep configuration while retaining some Expo conveniences, bridging the gap between Expo's fully managed workflow and pure React Native CLI, ideal for complex apps needing native customization without sacrificing all Expo benefits.
Prerequisites:
(1)Node.js (LTS)
node -v
npm -vIf missing → install from https://nodejs.org
(2) Watchman (Recommended on macOS)
brew install watchman(3) Xcode (if iOS)
From App Store → install → open once
4️⃣ Android Studio (if Android)
Make sure:
- SDK installed
- Emulator works
ANDROID_HOMEis set
Create Expo Bare App (Correct Way)
cd /path/to/your/folder
npx create-expo-app myBareApp --template bare-minimumHow to install native features in Expo Bare workflow:?
I consider an example of using location native feature.
There are steps regarding how to use in Expo Bare workflow
npx expo install expo-locationHow to convert existing Expo workflow project to Expo Bare workflow?
This process is called “ Ejecting to the bare workflow”
npx expo prebuildNote: Commit and push all changes to github/gitlab and then create a new branch and convert or execute the above command to convert to Expo Bare Workflow.
After the above process the app can be run on either Android / ios based on the below commands. The app is built as individual Android/ios app.
npx expo run:androidCreate React Native app with React native CLI:
npx @react-native-community/cli init MyReactNativeAppwithoutexpoIf Android build fails:
nano ~/.zshrc
Add these lines at the bottom:
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
Save:
Ctrl + O → Enter
Ctrl + X
Reload config::
source ~/.zshrcRun your app:
npx react-native start
In another terminal:
npx react-native run-androidPublishing React Native apps:
Steps to publish app developed With Expo(Managed or Bare workflow):
(1) Configure the Project
(2) Build app binaries with expo’s cloud service (Advantage: No local resources used and you can build for all target platforms)
(3) Submit to App stores (Manually or using cloud services provided by expo to make publishing process seamless)
Steps to publish app developed Without Expo:
(1) Configure the project
(2) Build app binaries locally
(3) Submit to App stores (Manually only)
Configuration for production:
(1) Permissions :
For Android( control permissions requested and shown in app store)
For iOS — Define permissions request text snippets.
(2) App Name & Identifier
set the Visible app name , an app version and a unique app identifier.
(3) Environment Variables: Store app wide variables securely (e.g . API keys)
(4) Icons & splash screens: configure and generate fitting icons and loading screens.
EAS:
Some of important links below for preparing app to publish using EAS service:
Adding Icons and A Splash Screen:
The below link contains info about Splash screen like dimensions and other info.
Build Expo apps with EAS:
(1) Create An Expo user account
(2)Install the latest EAS CLI
sudo npm install -g eas-cli(3) Log in to your Expo account
eas login(4) Configure the project
eas build:configureThe above command adds “eas.json” to our project.
(5)Run a build
Build for Android Emulator/device or iOS Simulator:The easiest way to try out EAS Build is to create a build that you can run on your Android device/emulator or iOS Simulator. It’s quicker than uploading it to a store, and you don’t need store developer membership accounts.
In order to run the build in Emulator plz follow below steps:
The below commands to be executed in this order :
npm install expo
npx expo doctor
npx expo install expo-dev-client
npx eas build --platform android --profile developmentWhat is a “profile” in EAS Build?
profile are defined in eas.json
Each profile describes:
how the app is built whether it’s for local dev, internal testing, or production whether it uses expo-dev-client whether it’s debuggable:
Example
{
"build": {
"development": { ... },
"preview": { ... },
"production": { ... }
}
}development vs preview (MOST IMPORTANT PART)
development profile
Purpose:
👉 For local development with Expo Dev Client
Typical characteristics:
- Uses
expo-dev-client - Debuggable
- Can connect to Metro bundler
- Similar to
expo start --dev-client
Used when:
- You want fast iteration
- You want console logs, hot reload, debugging
- You’re actively coding
eas build --platform android --profile developmentpreview profile
Purpose:
👉 For QA / testers / internal sharing
Typical characteristics:
- No dev menu
- No Metro bundler
- Standalone APK / AAB
- Still not production
Used when:
- You want to share the app with testers
- You want something closer to release
- You don’t need live reloading
eas build --platform android --profile previewproduction profile
Purpose:
👉 For Play Store release
Characteristics:
- Optimized
- Minified
- Signed for store
- No debugging
Example:
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"distribution": "store"
}
}
}Push Notifications:
Local Notifications:
Notifications that are triggered by installed app, for the local device. These notifications are not sent to any other users or devices.
Notifications:
https://docs.expo.dev/versions/latest/sdk/notifications/
npx expo install expo-notificationsStep 1:
Add configuration:
http://docs.expo.dev/versions/latest/sdk/notifications/#app-config
"plugins": [
[
"expo-notifications",
{
"icon": "./local/assets/notification_icon.png",
"color": "#ffffff",
"defaultChannel": "default",
"sounds": [
"./local/assets/notification_sound.wav",
"./local/assets/notification_sound_other.wav"
],
"enableBackgroundRemoteNotifications": false
}
]
]What changed (SDK 53+)
🚫 Expo Go no longer supports Android push notifications
Starting Expo SDK 53:
expo-notificationsremote / scheduled notifications- ❌ DO NOT work in Expo Go on Android
- ✅ ONLY work in a Development Build or Standalone app
That’s why you see:
“Android Push notifications functionality was removed from Expo Go”
This is by design, not a bug in your code.
(Recommended): Use Expo Development Build
npx expo install expo-dev-client
npx expo run:androidandroid/app/src/main/res/values/colors.xml
Add this EXACT content
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="iconBackground">#FFFFFF</color>
</resources>
Reason: In app.json you have:
Expo generated launcher XML that expects a named color resource, but it didn’t get created properly
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
}From your project root:
cd android
./gradlew clean
cd ..
npx expo prebuild --clean
npx expo run:android
final Code Fragment:
import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";
import * as Notifications from "expo-notifications";
import { useEffect } from "react";
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldPlaySound: false,
shouldSetBadge: false,
shouldShowAlert: true,
shouldShowBanner: true,
shouldShowList: true,
}),
});
export default function App() {
useEffect(() => {
async function setupNotifications() {
// Permission
await Notifications.requestPermissionsAsync();
// 🔴 REQUIRED for Android
if (Platform.OS === "android") {
await Notifications.setNotificationChannelAsync("default", {
name: "Default",
importance: Notifications.AndroidImportance.HIGH,
});
}
}
setupNotifications();
}, []);
async function scheduleNotificationHandler() {
await Notifications.scheduleNotificationAsync({
content: {
title: "Look at that notification",
body: "I'm so proud of myself!",
data: { userName: "CSM" },
},
trigger: {
seconds: 5,
channelId: "default",
},
});
}
return (
<View style={styles.container}>
<Button
title="Schedule Notification"
onPress={scheduleNotificationHandler}
/>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});Listen to incoming events:
useEffect(() => {
const subscription = Notifications.addNotificationReceivedListener(
(notification) => {
// console.log(`Notification Received ${notification}`);
console.log("Notification Received:", notification);
const userName = notification.request.content.data.userName;
console.log(userName);
},
);
return () => {
subscription.remove();
};Reacting to userinteractions with incoming notifications:
const responseSubscription =
Notifications.addNotificationResponseReceivedListener((response) => {
console.log("Notification Response Received:", response);
const userName = response.notification.request.content.data.userName;
console.log("Notification Response UserName:", userName);
});Push Notifications:
Test Push Notifications:






