Section 1: Getting Started
Installation
node Install
npx create-expo-app my-app --template blank
go to my-app folder
npm start
------
Expo Go - app view in andriod phone
-----
Android Studio (preview Android app view in laptop)
more action -> virtual device manager -> create device (include playstore template)
----
Xcode (preview Ios app view in laptop only work is Mac)
preference -> location -> comment line tools (select)
-----
npm start
a - Andiod
i - ios
r - manual reload app
Basic (Create Goals App)
https://reactnative.dev/docs/components-and-apis
style = {{
margin: 16,
borderWidth: 2,
borderColor: 'red'
}}
---------
<View style={styles.container}>
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Todo list with scrollView
import { StyleSheet, Text, View, TextInput, Button, ScrollView } from 'react-native';
import { useState } from 'react';
export default function App() {
const [goalInput, setGoalInput] = useState('')
const [goalsList, setGoalList] = useState([])
function handleGoalInput(entertedText) {
setGoalInput(entertedText);
}
function handleAddGoal () {
setGoalList(prevList => [
...prevList,
goalInput
])
setGoalInput('');
}
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<TextInput style={styles.input} placeholder="Type something" onChangeText={handleGoalInput} value={goalInput} />
<Button title="Add Goal" onPress={handleAddGoal}/>
</View>
<View style={styles.listContainer}>
<ScrollView >
{goalsList.map((goal, index) => (<Text style={styles.listDsn} key={index}>{goal}</Text>)) }
</ScrollView>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
listContainer:{
flex:4,
width:'100%',
padding: 15
},
inputContainer: {
flex:1,
flexDirection: 'row',
alignItems:'center',
},
input: {
borderColor: "#ccc",
borderBottomWidth: 1,
width: '70%',
marginRight: 15,
},
listDsn: {
marginBottom: 100,
fontSize: 16,
color: '#333',
},
});
Todo list with FlatList
import { StyleSheet, Text, View, TextInput, Button, FlatList } from 'react-native';
import { useState } from 'react';
export default function App() {
const [goalInput, setGoalInput] = useState('')
const [goalsList, setGoalList] = useState([])
function handleGoalInput(entertedText) {
setGoalInput(entertedText);
}
function handleAddGoal () {
setGoalList(prevList => [
...prevList,
{id : Math.random().toString(), value: goalInput}
])
// setGoalInput('');
}
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<TextInput style={styles.input} placeholder="Type something" onChangeText={handleGoalInput} value={goalInput} />
<Button title='Add Goal' onPress={handleAddGoal}/>
</View>
<View style={styles.listContainer}>
<FlatList
data={goalsList}
renderItem={(itemData) => {
return (
<Text style={styles.listDsn}> {itemData.item.value} </Text>
)
}}
keyExtractor={(item, index) => {
return item.id
}}
alwaysBouncerVertical={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
listContainer:{
flex:4,
width:'100%',
padding: 15
},
inputContainer: {
flex:1,
flexDirection: 'row',
alignItems:'center',
},
input: {
borderColor: "#ccc",
borderBottomWidth: 1,
width: '70%',
marginRight: 15,
},
listDsn: {
marginBottom: 100,
fontSize: 16,
color: '#333',
},
});
Todo list with FlatList, delete
import { StyleSheet, View, FlatList } from 'react-native';
import { useState } from 'react';
import GoalItem from './components/GoalItem'
import GoalInput from './components/GoalInput'
export default function App() {
const [goalsList, setGoalList] = useState([])
function handleGoalInput(input) {
setGoalList(prevList => [
...prevList,
{id : Math.random().toString(), value: input}
])
}
function handleDeleteGoal(id) {
setGoalList(prevList => {
return prevList.filter((goal) => goal.id !== id)
})
}
return (
<View style={styles.container}>
<GoalInput onDataReceived={handleGoalInput}/>
<View style={styles.listContainer}>
<FlatList
data={goalsList}
renderItem={(itemData) => {
return <GoalItem text={itemData.item} onDeleteItem={handleDeleteGoal}/>
}}
keyExtractor={(item, index) => {
return item.id
}}
alwaysBouncerVertical={false}
numColoumns={2}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
listContainer:{
flex:4,
width:'100%',
padding: 15
},
});
--------------
import { Text, StyleSheet } from 'react-native';
function GoalItem({ text, onDeleteItem }) {
return (
<Text style={styles.listDsn} onPress={onDeleteItem.bind(this, text.id)}>{text.value} {text.id}</Text>
);
}
export default GoalItem;
const styles = StyleSheet.create({
listDsn: {
marginBottom: 100,
fontSize: 16,
color: '#333',
},
})
---------------
import {View, TextInput, Button, StyleSheet} from 'react-native'
import { useState } from 'react';
function GoalInput({onDataReceived}) {
const [goalInput, setGoalInput] = useState('')
function handleGoalInput(entertedText) {
setGoalInput(entertedText);
}
function handleAddGoal () {
onDataReceived(goalInput)
// setGoalInput('');
}
return(
<View style={styles.inputContainer}>
<TextInput style={styles.input} placeholder="Type something" onChangeText={handleGoalInput} value={goalInput} />
<Button title='Add Goal' onPress={handleAddGoal}/>
</View>
)
}
export default GoalInput;
const styles = StyleSheet.create({
inputContainer: {
flex:1,
flexDirection: 'row',
alignItems:'center',
},
input: {
borderColor: "#ccc",
borderBottomWidth: 1,
width: '70%',
marginRight: 15,
},
})
Hover Effect
<Pressable
android_ripple={{color: '#ddd'}}
onPress={onDeleteItem.bind(this, text.id)}
style={({pressed}) => pressed && styles.pressedItem}>
Image
import { Image } from 'react-native';
<Image source={require('../asset/images/image.png')}>
Status bar
import { StatusBar } from 'react-native';
<StatusBar style="light">
To do list app
Layout
Screens/GameStart.js
components/PrimaryButton.js, Title.js
constants/colors.js
Boxshadow in Css
// Andoid
elevation: 4,
//iOS
shadowColor: 'black',
shadowOffset : {width: 0, height: 2},
shadowRadius : 6,
shadowOpacity : 0.25,
Input Text config
<TextInput style={styles.NumberInput}
maxLength={2}
keyboardType='number-pad' //show number keyboard
autoCapitalize="none" // Capitalize
autoCorrect={false} // it's true for email validation like that
/>
Button Hover Animation
<Pressable
style={({pressed}) =>
pressed // click color change for iOs
? [styles.btnInnercontainer, styles.pressed]
: styles.btnInnercontainer
}
onPress={pressHandler}
android_ripple={{color: '#640233'}} // click color change for android
>
Overall Code
import { StyleSheet, Text, View, Pressable } from 'react-native';
export default function App({children}) {
function pressHandler() {
console.log("Pressed")
}
return (
<View style={styles.btnOutercontainer}>
<Pressable
style={({pressed}) =>
pressed // click color change for iOs
? [styles.btnInnercontainer, styles.pressed]
: styles.btnInnercontainer
}
onPress={pressHandler}
android_ripple={{color: '#640233'}} // click color change for android
>
<Text style={styles.btnText}>{children}</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
btnOutercontainer: {
backgroundColor: 'orange',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 10,
overflow:'hidden'
},
btnInnercontainer: {
paddingVertical: 10,
paddingHorizontal: 20,
width: "100%",
},
pressed : {
color: '#cccccc'
},
btnText: {
color: 'white',
textAlign: "center"
}
});
Linear Gradient
npm install -g expo-cli
expo install expo-linear-gradient
import {LinearGradient} from 'expo-linear-gradient'
<LinearGradient
colors={['#ff7e5f', '#feb47b']} // Gradient colors
start={{ x: 0, y: 0 }} // Start position of the gradient
end={{ x: 1, y: 1 }} // End position of the gradient
>
<StartGameScreen />
</LinearGradient>
Background Image (opacity) with gradient
import { StyleSheet, View, ImageBackground } from 'react-native';
import {LinearGradient} from 'expo-linear-gradient'
<LinearGradient
colors={['#ff7e5f', '#feb47b']} // Gradient colors
start={{ x: 0, y: 0 }} // Start position of the gradient
end={{ x: 1, y: 1 }} // End position of the gradient
style={styles.rootScreen}
>
<ImageBackground
source={require('./assets/images/background.jpg')}
resizeMode="cover"
style={styles.rootScreen}
imageStyle={styles.backgroundImage}>
<StatusBar style="light"/>
<StartGameScreen />
</ImageBackground>
</LinearGradient>
const styles = StyleSheet.create({
rootScreen : {
flex: 1,
},
backgroundImage: {
opacity: 0.15
}
})
58. validation with alert
const [enteredNumber, setEnterNumber] = useState('');
function handleNumberInput (enteredText) {
setEnterNumber(enteredText)
}
function resetInputHandler () {
setEnterNumber('')
}
function confirmInputHandler () {
const chosenNumber = parseInt(enteredNumber)
if(isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99){
Alert.alert(
'Invalid number',
'Number has to be a number between 1 and 99',
[{text: 'Okay', style:'destructive', onPress: resetInputHandler}]
);
return ;
}
}
Switching Screens Programmatically
onPickNmber(enteredNumber)
export default function App({children, onPickNmber}) {
-----
function pickedNumberHandler(pickedNumber) {
setUserNumber(pickedNumber)
}
let screen = <StartGameScreen onPickNmber={pickedNumberHandler}/>
if(userNumber) {
screen = <GameScreen />
}
62. Safeareaview
In React Native, SafeAreaView is a component that ensures content is rendered within the safe area boundaries of a device. This is especially useful for devices with notches, status bars, or home indicators
import { StyleSheet, View, ImageBackground, SafeAreaView } from 'react-native';
<SafeAreaView style={styles.rootScreen}>
<StatusBar style="light"/>
{screen}
</SafeAreaView>
Colors Gobally
constants/colors.js
const Colors = {
primary500: '#73063c',
primary800: '#640233'
}
export default Colors
import Colors from '../constants/colors'
borderBottomColor: Colors.primary500,
65. Displaying Random Numbers
function generateRandomBetween (min, max, exclude) {
const rndNum = Math.floor(Math.random() * (max - min)) + min;
if (rndNum === exclude) {
return generateRandomBetween(min, max, exclude)
} else {
return rndNum;
}
}
const initialGuess = generateRandomBetween (1, 100, userNumber)
66. Adding Game Control Buttons ("+" & "-") to the App
import { StyleSheet, Text, View, Alert } from 'react-native';
import {useState} from 'react'
import PrimaryButton from '../components/PrimaryButton'
function generateRandomBetween (min, max, exclude) {
const rndNum = Math.floor(Math.random() * (max - min)) + min;
if (rndNum === exclude) {
return generateRandomBetween(min, max, exclude)
} else {
return rndNum;
}
}
let minBoundary = 1;
let maxBoundary = 100
export default function App({userNumber}) {
const initialGuess = generateRandomBetween (minBoundary, maxBoundary, userNumber)
const [guessNum, setGuessNum] = useState(initialGuess)
function nextGuessHandler(direction) {
if(
(direction === "lower" && guessNum < userNumber) ||
(direction === "greater" && guessNum > userNumber)
) {
Alert.alert("Don't lie!", 'you know that this is wrong...',
[{text:'Sorry', style: 'cancel'}])
return ;
}
if(direction === "lower") {
maxBoundary = guessNum
} else {
minBoundary = guessNum + 1
}
const newRndNumber = generateRandomBetween(minBoundary, maxBoundary, userNumber)
setGuessNum(newRndNumber)
}
return (
<View style={styles.container}>
<Text>Opponents Guess</Text>
<Text>{guessNum}</Text>
<View style={styles.btnContainer}>
<View style={styles.btnCol}>
<PrimaryButton onPress={nextGuessHandler.bind(this, 'greater')}>+</PrimaryButton>
</View>
<View style={styles.btnCol}>
<PrimaryButton onPress={nextGuessHandler.bind(this, 'lower')}>-</PrimaryButton>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
btnContainer: {
flexDirection: "row"
},
btnCol : {
flex: 1,
marginHorizontal : 10
}
});
UseEffect
export default function App({userNumber, onGameOver}) {
useEffect(() => {
if(parseInt(guessNum) === parseInt(userNumber)) {
onGameOver()
}
}, [guessNum, userNumber, onGameOver ])
----
const [gameOver, setGameOver] = useState(false)
function handleGameOverScreen() {
setGameOver(true)
}
let screen = <StartGameScreen onPickNmber={pickedNumberHandler}/>
if(userNumber) {
screen = <GameScreen userNumber={userNumber} onGameOver={handleGameOverScreen}/>
}
if (gameOver && userNumber) {
screen = <GameOverScreen/>
}
Cascading Styles
export default function App({children, onPress, style}) {
<Text style={[styles.btnText, style]}>{children}</Text>
----
<PrimaryButton style={styles.btnBlkCl} onPress={resetInputHandler}>reset</PrimaryButton>
Icons
import {Ionicons} from '@expo/vector-icons';
<Ionicons name="add-circle-outline" size={24} color="black" />
Use Font
npm install expo-font
npm install expo-app-loading
/assets/fonts/Raleway.ttf
import { useFonts } from 'expo-font';
import AppLoading from 'expo-app-loading'
const [fontsLoaded] = useFonts({
'Raleway': require('./assets/fonts/Raleway.ttf'),
});
if (!fontsLoaded) {
return <AppLoading />;
}
rootScreen : {
flex: 1,
fontFamily : 'Raleway'
},
Value pass
//GameOverScreen.js
export default function App({roundNumber, userNumber, onStartGame})
<Text style={styles.description}>Your phone needed
<Text style={styles.highlight}>{roundNumber}</Text> rounds to guess the number
<Text style={styles.highlight}>{userNumber}</Text>
</Text>
<View>
<PrimaryButton onPress={onStartGame}>Start New Game </PrimaryButton>
</View>
//App.js
const [roundNumber, setRoundNumber] = useState(0)
function handleStartNewGame() {
setUserNumber(null);
setRoundNumber(0)
}
if (gameOver && userNumber) {
screen = <GameOverScreen userNumber={userNumber} roundNumber={roundNumber} onStartGame={handleStartNewGame} />
}
//GameScreen.js
useEffect(() => {
minBoundary =1;
maxBoundary = 100;
}, [])
//GameScreen.js
const [guessNumberList, setGuessNumberList] = useState([])
function nextGuessHandler(direction) {
if(
(direction === "lower" && guessNum < userNumber) ||
(direction === "greater" && guessNum > userNumber)
) {
Alert.alert("Don't lie!", 'you know that this is wrong...',
[{text:'Sorry', style: 'cancel'}])
return ;
}
if(direction === "lower") {
maxBoundary = guessNum
} else {
minBoundary = guessNum + 1
}
const newRndNumber = generateRandomBetween(minBoundary, maxBoundary, userNumber)
setGuessNum(newRndNumber)
setGuessNumberList(prevData => [...prevData, newRndNumber])
}
<FlatList data={guessNumberList}
renderItem={(itemData) => {
return (
<Text>{itemData.item}</Text>
)
}}
keyExtractor={(item, index) => {
return index
}}
alwaysBouncerVertical={false}
/>
onGameOver(guessNumberList.length)
//App.js
function handleGameOverScreen(number) {
setGameOver(true)
setRoundNumber(number)
}
Game app
Dimensions API
Dynamic change value based of widow size
import { Dimensions } from 'react-native';
const deviceHeight = Dimensions.get('window').height
padddingTop : deviceHeight < 380 ? 12 : 24
Screen Orientation Problems(Auto rotate)
//App.json
"orientation": "auto",
Setting size dynamically
import { useWindowDimensions } from 'react-native';
const {width, height} = useWindowDimensions()
const marginTopDistance = height < 500 ? 30 : 70
style={[styles.rootScreen, {marginTop: marginTopDistance}]}
86. Managing Screen Content with KeyboardAvoidingView
import { ScrollView, KeyboardAvoidingView } from 'react-native';
<ScrollView style={styles.screen}>
<KeyboardAvoidingView style={styles.screen} behavior="position">
...Code
</KeyboardAvoidingView>
</ScrollView>
Improving the Landscape Mode UI
import { useWindowDimensions } from 'react-native';
const {width, height} = useWindowDimensions()
let content = <>
<Text>{guessNum}</Text>
</>
if(width > 500) {
content = <>
<Text>{guessNum2}</Text>
</>
}
{content}
ImageSize with useWindowDimensions
import { useWindowDimensions } from 'react-native';
const {width, height} = useWindowDimensions()
let imageSize = 300;
if(width < 380) {imageSize = 150}
if(height < 400) {imageSize = 80}
const imageStyle = {
width : imageSize,
height : imageSize,
borderRadius : imageSize / 2
}
style={[styles.imageContainer, imageStyle]}
Writing Platform-specific code with the Platform API
// method 1
borderWidth : Platform.Os === 'android' ? 2 : 0
// method 2
borderWidth : Platform.select({ios:0, android:2})
// method 3
Title.android.js
borderWidth : 2
Title.ios.js
borderWidth : 0
import Title form './../Title'
Navigation Package
npm install @react-navigation/native
expo install react-native-screens react-native-safe-area-context
expo install @react-navigation/native-stack
// App.js
import {NavigationContainer} from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator();
<NavigationContainer>
<Stack.Navigator
initialRouteName="MealsCategories" // Set initial screen
>
<Stack.Screen name="MealsCategories" component={CategoriesScreen} />
</Stack.Navigator>
</NavigationContainer>
Implementing Navigation Between Two Screes
//App.js
const Stack = createNativeStackNavigator();
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="MealsCategories" component={CategoriesScreen} />
<Stack.Screen name="MealsOverViewScreen" component={MealsOverviewScreen} />
</Stack.Navigator>
</NavigationContainer>
//MealsOverviewScreen.js
import { StyleSheet, FlatList, View } from 'react-native';
import MealsGridTile from '../components/MealsGridTile';
import {CATEGORIES, MEALS} from '../data/dummy-data'
function renderCategoryItem (itemData) {
const item = itemData.item
const mealsProps = {
title : item.title,
color : item.color,
imageUrl : item.imageUrl
}
return <MealsGridTile {...mealsProps}/>
}
export default function MealsOverviewScreen() {
return (
<View>
//<Image source={{url: imageUrl}} />
<FlatList
data={MEALS}
keyExtracter={(item) => item.id}
renderItem={renderCategoryItem}
numColumns={2}
/>
</View>
);
}
//CategoriesScreen.js
export default function CategoriesScreen({navigation}) {
function renderCategoryItem (itemData) {
function pressHandler() {
navigation.navigate('MealsOverViewScreen');
}
return <CategoryGridTile
title={itemData.item.title}
color={itemData.item.color}
onPress={pressHandler}
/>
}
return (
<FlatList
data={CATEGORIES}
keyExtractor={(item) => item.id}
renderItem={renderCategoryItem}
numColumns={2}
/>
);
}
Pass Id through route
//MealsOverviewScreen.js
export default function CategoriesScreen({navigation}) {
function pressHandler() {
navigation.navigate('MealsOverViewScreen', {
categoryId : itemData.item.id
});
}
//CategoriesScreen.js
export default function MealsOverviewScreen({route}) {
const CatId = route.params.categoryId
filter categoryId based
CatId = C1,
new Meal(
'm1',
['c1', 'c2'], // CategoryIds
'Spaghetti with Tomato Sauce',
//CategoriesScreen.js
export default function MealsOverviewScreen({route}) {
const CatId = route.params.categoryId
const displayedMeals = MEALS.filter((mealItem) => {
return mealItem.categoryIds.indexOf(CatId) >= 0;
})
Pass Id and filter from Data
export default function MealsGridTile() {
const navigation = useNavigation()
function handleMealDetailView() {
navigation.navigate('MealDetailScreen', {
mealId : itemData.item.id
})
}
------
export default function MealsOverviewScreen({route, navigation}) {
const mealId = route.params.mealId
const selectedMeal = MEALS.find((meal) => meal.id === mealId)
return (
<View>
<Text>{selectedMeal.title} ({mealId})</Text>
</View>
);
}
Styling Screen Headers and backgrounds, navigation title dynamically
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#4a148c' }, // Custom header background color
headerTintColor: 'white', // Tint color for back button and title
contentStyle: { backgroundColor: '#fedeff' }, // Background color for screen
}}
>
<Stack.Screen
name="MealsCategories"
component={CategoriesScreen}
options={{ title: 'Meal Categories' }} // Custom title
/>
<Stack.Screen
name="MealsOverViewScreen"
component={MealsOverviewScreen}
options={{ title: 'Meals Overview' }}
/>
</Stack.Navigator>
</NavigationContainer>
navigation title dynamically
Option 1
<Stack.Screen
name="MealsOverviewScreen"
component={MealsOverviewScreen}
options={({ route }) => ({
title: route.params.categoryTitle, // Dynamic title from params
})}
/>
navigation.navigate('MealsOverviewScreen', { categoryTitle: 'Italian Cuisine' });
Option 2
navigation.navigate('MealsOverviewScreen', { categoryId : itemData.item.id });
----
export default function MealsOverviewScreen({route, navigation}) {
const CatId = route.params.categoryId
useLayoutEffect(() => { // may use UseEffect
const categoryTitle = CATEGORIES.find(category => category.id === CatId).title
navigation.setOptions({
title: categoryTitle, // Dynamically update the header title
});
}, [navigation, CatId]);
header buttons
import { useLayoutEffect } from 'react';
import { Ionicons } from '@expo/vector-icons'; // Import Ionicons for the star icon
export default function MealsOverviewScreen({route, navigation}) {
const mealId = route.params.mealId
useLayoutEffect(() => {
navigation.setOptions({
title: selectedMeal.title || 'Meals',
headerRight: () => (
<Pressable
onPress={() => alert('Added to favorites!')}
style={({ pressed }) => [{ opacity: pressed ? 0.5 : 1 }]}
>
<Ionicons name="star" size={24} color="yellow" />
</Pressable>
),
});
}, [navigation, selectedMeal]);
const selectedMeal = MEALS.find((meal) => meal.id === mealId)
return (
<View>
<Text>{selectedMeal.title} ({mealId})</Text>
</View>
);
}
Drawer Nav bar
npm install react-native-reanimate d@1 --save --save-exact
npm install @react-navigation/drawer
expo install react-native-gesture-handler react-native-reanimated
npm install react-native-reanimated@1 --save --save-exact
import { StatusBar } from 'expo-status-bar';
import CategoriesScreen from './screens/CategoriesScreen';
import MealsOverviewScreen from './screens/MealsOverviewScreen';
import MealDetailScreen from './screens/MealDetailScreen';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Ionicons } from '@expo/vector-icons'; // Import icons
const Drawer = createDrawerNavigator();
export default function App() {
return (
<>
<StatusBar style="light" />
<NavigationContainer>
<Drawer.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#4a148c' },
headerTintColor: 'white',
contentStyle: { backgroundColor: '#fedeff' },
drawerStyle: {
backgroundColor: '#c6cbef', // background color
width: 270,
},
drawerActiveTintColor: '#e91e63', // Color for active item
drawerInactiveTintColor: 'black', // Color for inactive items
drawerActiveBackgroundColor: '#ddd', // Active background color
drawerLabelStyle: {
fontSize: 16,
fontWeight: 'bold',
},
}}
>
<Drawer.Screen
name="MealsCategories"
component={CategoriesScreen}
options={{
title: 'Meal Categories',
drawerIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} /> // Icon for this screen
),
}}
/>
<Drawer.Screen
name="MealsOverviewScreen"
component={MealsOverviewScreen}
options={{
title: 'Meals Overview',
drawerIcon: ({ color, size }) => (
<Ionicons name="star" size={size} color={color} /> // Icon for this screen
),
}}
/>
<Drawer.Screen
name="MealDetailScreen"
component={MealDetailScreen}
options={{
title: 'Meal Detail',
drawerIcon: ({ color, size }) => (
<Ionicons name="admin" size={size} color={color} /> // Icon for this screen
),
}}
/>
</Drawer.Navigator>
</NavigationContainer>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
Button to toggle Drawer Nav bar
const CategoriesScreen = ({ navigation }) => {
function handleNavbar () {
navigation.toggleDrawer()
}
Bottom tab Nav bar
npm install @react-navigation/bottom-tabs
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { NavigationContainer } from '@react-navigation/native';
import { View, Text } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
// Dummy Screens
const HomeScreen = () => (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home Screen</Text>
</View>
);
const SettingsScreen = () => (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings Screen</Text>
</View>
);
// Create Tab Navigator
const Tab = createBottomTabNavigator();
const App = () => {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = 'home-outline';
} else if (route.name === 'Settings') {
iconName = 'settings-outline';
}
return <Icon name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
headerShown: false,
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
};
export default App;
Nested Nav
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Ionicons } from '@expo/vector-icons';
import CategoriesScreen from './screens/CategoriesScreen'
import MealsOverviewScreen from './screens/MealsOverviewScreen'
import MealDetailScreen from './screens/MealDetailScreen'
import FavoritesScreen from './screens/FavoritesScreen'
const Stack = createNativeStackNavigator();
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#351401' },
headerTintColor: 'white',
sceneContainerStyle: { backgroundColor: '#3f2f25' },
drawerContentStyle: { backgroundColor: '#351401' },
drawerInactiveTintColor: 'white',
drawerActiveTintColor: '#351401',
drawerActiveBackgroundColor: '#e4baa1',
}}
>
<Drawer.Screen
name="Categories"
component={CategoriesScreen}
options={{
title: 'All Categories',
drawerIcon: ({ color, size }) => (
<Ionicons name="list" color={color} size={size} />
),
}}
/>
<Drawer.Screen
name="Favorites"
component={FavoritesScreen}
options={{
drawerIcon: ({ color, size }) => (
<Ionicons name="star" color={color} size={size} />
),
}}
/>
</Drawer.Navigator>
);
}
export default function App() {
return (
<>
<StatusBar style="light" />
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#351401' },
headerTintColor: 'white',
contentStyle: { backgroundColor: '#3f2f25' },
}}
>
<Stack.Screen
name="Drawer"
component={DrawerNavigator}
options={{
headerShown: false,
}}
/>
<Stack.Screen name="MealsOverviewScreen" component={MealsOverviewScreen} />
<Stack.Screen
name="MealDetailScreen"
component={MealDetailScreen}
options={{
title: 'About the Meal',
}}
/>
</Stack.Navigator>
</NavigationContainer>
</>
);
}
const styles = StyleSheet.create({
container: {},
});
Context API
import { createContext,useState,useLayoutEffect } from 'react';
export const FavoritiesContext = createContext({
ids: [],
addFavorite: (id) => {},
removeFavorite: (id) => {}
})
function FavoritiesContextProvider ({children}) {
const [favoriteMealIds, setFavoriteMealIds] = useState([]);
function addFavorite(id) {
if (!favoriteMealIds.includes(id)) {
setFavoriteMealIds((currentFavIds) => [...currentFavIds, id])
} else {
setFavoriteMealIds((currentFavIds) =>
currentFavIds.filter((mealId) => mealId !== id)
)
}
}
function removeFavorite(id) {
setFavoriteMealIds((currentFavIds) =>
currentFavIds.filter((mealId) => mealId !== id)
)
}
useLayoutEffect(()=>{
console.log("favoriteMealIds",favoriteMealIds)
}, [favoriteMealIds])
const value = {
ids: favoriteMealIds,
addFavorite: addFavorite,
removeFavorite: removeFavorite
}
return <FavoritiesContext.Provider value={value}>{children}</FavoritiesContext.Provider>
}
export default FavoritiesContextProvider
---
App.js
import FavoritiesContextProvider from './store/context/favorities-context'
export default function App() {
return (
<>
<StatusBar style="light" />
<FavoritiesContextProvider>
<NavigationContainer>
</NavigationContainer>
</FavoritiesContextProvider>
</>
)}
----
import {FavoritiesContext} from '../store/context/favorities-context'
export default function MealsOverviewScreen({route, navigation}) {
const favoriteMealsCtx = useContext(FavoritiesContext);
// if (!favoriteMealsCtx) {
// console.error('FavoritesContext is undefined! Make sure you wrapped the component inside FavoritesContextProvider.');
// }
const mealId = route.params.mealId
const selectedMeal = MEALS.find((meal) => meal.id === mealId)
const mealIsfavorite = favoriteMealsCtx.ids.includes(mealId)
function changeFavoriesStatusHandler() {
console.log("mealId", mealId)
if (mealIsfavorite) {
favoriteMealsCtx.removeFavorite(mealId);
} else {
favoriteMealsCtx.addFavorite(mealId);
}
}
useLayoutEffect(() => {
navigation.setOptions({
title: selectedMeal.title || 'Meals',
headerRight: () => (
<Pressable
onPress={changeFavoriesStatusHandler}
style={({ pressed }) => [{ opacity: pressed ? 0.5 : 1 }, styles.paddingbtn]}
>
<Ionicons name={mealIsfavorite ? 'star' : 'star-outline'} size={24} color="yellow" />
</Pressable>
),
});
}, [navigation, selectedMeal, mealIsfavorite, changeFavoriesStatusHandler]);
return (
<View>
<Text>{selectedMeal.title} ({mealId})</Text>
<Pressable
onPress={changeFavoriesStatusHandler}
style={({ pressed }) => [{ opacity: pressed ? 0.5 : 1 }, styles.paddingbtn]}
>
<Ionicons name={mealIsfavorite ? 'star' : 'star-outline'} size={24} color="yellow" />
</Pressable>
</View>
);
}