Mastering SafeAreaView, interactive TextInput forms, and high-performance FlatList virtualization with NativeWind.
Altamash, session complete hone ke baad bhi ye quick mental model hamesha yaad rakhna:
Modern phones me screen ka content hardware notch ya camera punch-hole ke peeche chip jata hai. <SafeAreaView> phone ke sensors detect karke utna exact top/bottom padding auto-apply karta hai.
Web me e.target.value nikalna padta tha. React Native me onChangeText={(text) => setText(text)} seedha direct string deta hai! Saath hi keyboardType="numeric" se phone ka number keypad popup hota hai.
Agar 1,000 items render karne ho toh ScrollView phone ko freeze kar dega. FlatList sirf screen par dikhne wale 5–10 items render karta hai aur scroll hote hi purane views recycle kar leta hai.
1. Protect your UI from notch clipping using SafeAreaView.
2. Capture and validate text/numeric user input with controlled TextInput.
3. Build memory-efficient virtualized lists using FlatList.
4. Build the complete live MacroZone Meal Logger & Calorie Tracker in app/index.tsx.
On modern smartphones (iPhone Dynamic Island, camera notches, Android home indicator bars), screen content can easily slide underneath physical hardware cutouts if unmanaged.
Header title gets obscured behind the phone's physical hardware camera cutout.
Automatically adds top/bottom padding matching the exact device hardware insets.
import { SafeAreaView } from "react-native-safe-area-context"; // 👈 Official safe area provider import
import { View, Text } from "react-native"; // 👈 Core React Native primitives
export default function SafeContainer() {
return (
// 👈 flex-1: full screen height lega, bg-slate-950: deep dark background
<SafeAreaView className="flex-1 bg-slate-950">
{/* 👈 px-5 py-4: notch ke neeche 20px horizontal aur 16px vertical safe padding */}
<View className="px-5 py-4">
<Text className="text-white text-xl font-bold">
MacroZone Dashboard
</Text>
</View>
</SafeAreaView>
);
}
| Code Line | Kya Karta Hai? (Simple Explanation) |
|---|---|
import { SafeAreaView } from "react-native-safe-area-context" |
Official Expo & React Native recommended safe area provider import karta hai. |
<SafeAreaView className="flex-1 bg-slate-950"> |
Screen ko full height (flex-1) deta hai aur notch ke neeche automatic padding lagata hai. |
className="px-5 py-4" |
Content ko screen ke edges se clean 20px horizontal aur 16px vertical breathing space deta hai. |
In Web HTML, you handle text inputs using <input type="text" onChange={(e) => e.target.value}>. In React Native, the behavior is much cleaner and mobile-tailored:
💡 Key Differences from Web:
onChangeText directly sends the plain string value (e.g. onChangeText={(text) => setName(text)}). You don't have to extract e.target.value!keyboardType="numeric" to instantly pop up the mobile number pad for calories/macros.placeholderTextColor="#64748b" to style the placeholder in dark mode.import { useState } from "react"; // 👈 State hook import
import { View, Text, TextInput, Pressable } from "react-native"; // 👈 Primitives import
export default function MealInputForm() {
const [mealName, setMealName] = useState(""); // 👈 Food name ke liye string state
const [calories, setCalories] = useState(""); // 👈 Calories ke liye string state
const handleAdd = () => {
// 👈 Basic validation: Empty strings ko submit hone se rokta hai
if (!mealName.trim() || !calories.trim()) return;
alert(`Added: ${mealName} (${calories} kcal)`);
setMealName(""); // 👈 Form input clear karta hai
setCalories(""); // 👈 Calorie input clear karta hai
};
return (
// 👈 Card container: rounded corners, border aur internal gap
<View className="bg-slate-900 border border-slate-800 p-4 rounded-2xl gap-3">
<Text className="text-slate-200 font-semibold text-base">Log New Meal</Text>
{/* 👈 Meal Name Input: Controlled input bound to mealName state */}
<TextInput
value={mealName} // 👈 State variable se binded
onChangeText={setMealName} // 👈 Seedha typed string state me save hoti hai
placeholder="e.g. Grilled Chicken Salad"
placeholderTextColor="#64748b" // 👈 Dark mode placeholder color
className="bg-slate-950 border border-slate-800 text-white px-4 py-3 rounded-xl text-base"
/>
{/* 👈 Calories Input: Opens numeric keyboard on phone */}
<TextInput
value={calories}
onChangeText={setCalories} // 👈 Number string save hoti hai
placeholder="Calories (kcal)"
placeholderTextColor="#64748b"
keyboardType="numeric" // 👈 Phone ka dedicated number keypad open karta hai
className="bg-slate-950 border border-slate-800 text-white px-4 py-3 rounded-xl text-base"
/>
{/* 👈 Submit Button: active:bg-indigo-700 gives instant touch feedback */}
<Pressable
onPress={handleAdd}
className="bg-indigo-600 active:bg-indigo-700 py-3 rounded-xl items-center mt-1"
>
<Text className="text-white font-semibold">Add to Log</Text>
</Pressable>
</View>
);
}
| Code Prop / Line | Kya Karta Hai? (Simple Explanation) |
|---|---|
value={mealName} |
Input field ko React ke state variable se bind karta hai (Controlled Input pattern). |
onChangeText={setMealName} |
User ke har keystroke par direct string pass karke state update karta hai. |
placeholderTextColor="#64748b" |
Dark mode ke andar placeholder text ko readable slate-grey color deta hai. |
keyboardType="numeric" |
Phone ka dedicated numeric keyboard open karta hai taaki user numbers jaldi type kar sake. |
When building mobile apps, never use array.map() inside a simple ScrollView for long or dynamic data. FlatList is a virtualized list engine that recycles views as the user scrolls.
FlatList only creates native views for the 5–10 items currently visible on screen.
When an item scrolls off the top, its underlying native view is reused for new items appearing at the bottom.
import { FlatList, View, Text } from "react-native";
// 👈 Initial mock data array
const MEALS_DATA = [
{ id: "1", name: "Oats & Whey Protein", calories: 420, protein: 35 },
{ id: "2", name: "Boiled Eggs & Toast", calories: 340, protein: 24 },
{ id: "3", name: "Grilled Chicken Breast", calories: 510, protein: 55 },
];
export default function MealList() {
return (
<FlatList
data={MEALS_DATA} // 👈 1. Data source array
keyExtractor={(item) => item.id} // 👈 2. Unique string ID (React reconciliation ke liye)
showsVerticalScrollIndicator={false} // 👈 3. Clean mobile look ke liye scrollbar hide karta hai
renderItem={({ item }) => ( // 👈 4. Har individual row ka UI design
<View className="bg-slate-900 border border-slate-800 p-4 rounded-2xl flex-row justify-between items-center mb-3">
<View>
<Text className="text-white font-semibold text-base">{item.name}</Text>
<Text className="text-slate-400 text-xs mt-0.5">Protein: {item.protein}g</Text>
</View>
<Text className="text-indigo-400 font-bold text-base">{item.calories} kcal</Text>
</View>
)}
ListEmptyComponent={ // 👈 5. Jab data array khali ho tab ye fallback dikhega
<Text className="text-slate-500 text-center py-6">No meals logged yet.</Text>
}
/>
);
}
| Prop Name | Purpose & Best Practice |
|---|---|
data |
Array of items to render (e.g. MEALS_DATA). |
keyExtractor |
Function returning a unique string ID for each item (e.g. (item) => item.id). Prevents rendering bugs. |
renderItem |
Function that receives { item, index } and returns the JSX component for that individual row. |
ListEmptyComponent |
JSX element rendered automatically when data array is empty (e.g., "No meals logged yet"). |
If you're new to Tailwind CSS, don't worry! Tailwind uses a predictable mathematical system. Once you understand the 4 core rules below, you can style any mobile UI in seconds:
In Tailwind, spacing numbers are multiples of 4px (Number × 4 = Total Pixels):
p-1 = 4px padding • p-2 = 8px • p-3 = 12px • p-4 = 16px • p-5 = 20px • p-6 = 24pxpx-5 = Horizontal padding (Left + Right = 20px)py-3 = Vertical padding (Top + Bottom = 12px)pt-2 = Padding Top (8px) • mb-4 = Margin Bottom (16px)gap-2.5 = Flex gap between items (2.5 × 4px = 10px)To create depth in dark mode without harsh solid black, we layer different shades of Slate:
bg-slate-950 (#020617) = Deepest background base layer (Full screen).bg-slate-900 (#0f172a) = Elevated surface card layer.border-slate-800 (#1e293b) = 1px subtle divider border to separate cards from background.text-slate-400 (#94a3b8) = Muted secondary labels & metadata.text-white (#ffffff) = High-contrast primary headings.bg-indigo-500/20 = Indigo color with 20% opacity (creates a glowing badge background).active:bg-indigo-700 = When user presses finger on the screen, automatically darken button to Indigo-700 for native touch feedback!rounded-3xl = 24px squircle border radius (iOS luxury card look).Here is the complete, interactive component combining SafeAreaView, TextInput state, dynamic FlatList rendering, and fully decoded Tailwind classes:
import { useState } from "react";
import { View, Text, TextInput, Pressable, FlatList } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; // 👈 Safe area wrapper
// 👈 TypeScript Interface: Meal item ki structure define karta hai
interface MealItem {
id: string;
name: string;
calories: number;
protein: number;
}
export default function Index() {
// 👈 Meals list ki state: initial sample meals ke saath
const [meals, setMeals] = useState<MealItem[]>([
{ id: "1", name: "Oats & Whey Protein", calories: 420, protein: 35 },
{ id: "2", name: "Grilled Chicken Salad", calories: 510, protein: 48 },
{ id: "3", name: "Boiled Eggs & Almonds", calories: 280, protein: 18 },
]);
// 👈 Form input states
const [name, setName] = useState("");
const [calories, setCalories] = useState("");
const [protein, setProtein] = useState("");
// 👈 Dynamic Aggregation: Har render par total calories aur protein auto-calculate hota hai
const totalCalories = meals.reduce((sum, m) => sum + m.calories, 0);
const totalProtein = meals.reduce((sum, m) => sum + m.protein, 0);
// 👈 Naya meal add karne ka handler
const handleAddMeal = () => {
if (!name.trim() || !calories.trim()) return; // Validation
const newMeal: MealItem = {
id: Date.now().toString(), // Unique timestamp ID
name: name.trim(),
calories: parseInt(calories) || 0,
protein: parseInt(protein) || 0,
};
setMeals([newMeal, ...meals]); // 👈 List ke top par add karta hai
setName(""); // Input reset
setCalories("");
setProtein("");
};
// 👈 Meal delete karne ka handler
const handleDeleteMeal = (id: string) => {
setMeals(meals.filter((m) => m.id !== id)); // 👈 Clicked ID ko filter out karta hai
};
return (
// 👈 Tailwind: flex-1 (full screen), bg-slate-950 (deepest dark), px-5 (20px left-right padding), pt-2 (8px top padding)
<SafeAreaView className="flex-1 bg-slate-950 px-5 pt-2">
{/* ── 1. Top Header Dashboard (Daily Progress) ── */}
{/* 👈 Tailwind: bg-slate-900 (card), border border-slate-800 (1px border), rounded-3xl (24px iOS curve), p-5 (20px padding), mb-4 (16px bottom margin) */}
<View className="bg-slate-900 border border-slate-800 rounded-3xl p-5 mb-4 shadow-2xl">
{/* 👈 Tailwind: flex-row (horizontal layout), items-center (vertically centered), justify-between (space apart) */}
<View className="flex-row items-center justify-between mb-3">
<View>
{/* 👈 Tailwind: text-xs (12px), font-bold, uppercase, tracking-wider (letter spacing), text-indigo-400 (purple accent) */}
<Text className="text-xs font-bold uppercase tracking-wider text-indigo-400">
Daily Progress
</Text>
{/* 👈 Tailwind: text-2xl (24px), font-bold, text-white */}
<Text className="text-2xl font-bold text-white">
MacroZone Tracker
</Text>
</View>
{/* 👈 Tailwind: w-11 h-11 (44x44px square), rounded-2xl (16px curve), bg-indigo-500/20 (20% glowing purple), border-indigo-500/30 */}
<View className="w-11 h-11 rounded-2xl bg-indigo-500/20 border border-indigo-500/30 items-center justify-center">
<Text className="text-xl">🥗</Text>
</View>
</View>
{/* 👈 Tailwind: flex-row (horizontal row), gap-3 (12px space between boxes) */}
<View className="flex-row gap-3">
{/* 👈 Tailwind: flex-1 (50% equal width), bg-slate-950/80 (inner dark inset), border border-slate-800, p-3 (12px padding), rounded-2xl */}
<View className="flex-1 bg-slate-950/80 border border-slate-800 p-3 rounded-2xl">
<Text className="text-slate-400 text-xs">Total Calories</Text>
<Text className="text-white text-xl font-bold mt-1">
{totalCalories} <Text className="text-xs text-indigo-400 font-normal">kcal</Text>
</Text>
</View>
<View className="flex-1 bg-slate-950/80 border border-slate-800 p-3 rounded-2xl">
<Text className="text-slate-400 text-xs">Total Protein</Text>
<Text className="text-white text-xl font-bold mt-1">
{totalProtein} <Text className="text-xs text-emerald-400 font-normal">g</Text>
</Text>
</View>
</View>
</View>
{/* ── 2. Interactive Input Form ── */}
{/* 👈 Tailwind: gap-2.5 (10px gap between input rows) */}
<View className="bg-slate-900/90 border border-slate-800 p-4 rounded-2xl mb-4 gap-2.5">
{/* 👈 Food Name Input: px-3.5 (14px padding), py-2.5 (10px vertical), rounded-xl (12px border radius) */}
<TextInput
value={name}
onChangeText={setName}
placeholder="Food Name (e.g. Greek Yogurt Bowl)"
placeholderTextColor="#64748b"
className="bg-slate-950 border border-slate-800 text-white px-3.5 py-2.5 rounded-xl text-sm"
/>
{/* 👈 Horizontal row for calories & protein inputs */}
<View className="flex-row gap-2.5">
<TextInput
value={calories}
onChangeText={setCalories}
placeholder="Calories (kcal)"
placeholderTextColor="#64748b"
keyboardType="numeric"
className="flex-1 bg-slate-950 border border-slate-800 text-white px-3.5 py-2.5 rounded-xl text-sm"
/>
<TextInput
value={protein}
onChangeText={setProtein}
placeholder="Protein (g)"
placeholderTextColor="#64748b"
keyboardType="numeric"
className="flex-1 bg-slate-950 border border-slate-800 text-white px-3.5 py-2.5 rounded-xl text-sm"
/>
</View>
{/* 👈 Add Meal Button: bg-indigo-600, active:bg-indigo-700 (press effect), py-3 (12px height), rounded-xl */}
<Pressable
onPress={handleAddMeal}
className="bg-indigo-600 active:bg-indigo-700 py-3 rounded-xl items-center mt-1"
>
<Text className="text-white font-semibold text-sm">
+ Add Meal to Log
</Text>
</Pressable>
</View>
{/* ── 3. High-Performance Virtualized FlatList ── */}
<Text className="text-slate-400 text-xs font-bold uppercase tracking-wider mb-2">
Logged Meals ({meals.length})
</Text>
<FlatList
data={meals}
keyExtractor={(item) => item.id}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<View className="p-8 items-center justify-center bg-slate-900/40 border border-slate-800/50 rounded-2xl">
<Text className="text-slate-500 text-sm">No meals logged yet today.</Text>
</View>
}
renderItem={({ item }) => (
<View className="bg-slate-900 border border-slate-800/80 p-3.5 rounded-2xl flex-row justify-between items-center mb-2.5">
<View className="flex-1 mr-2">
<Text className="text-white font-medium text-sm">{item.name}</Text>
<Text className="text-slate-400 text-xs mt-0.5">
Protein: <Text className="text-emerald-400 font-semibold">{item.protein}g</Text>
</Text>
</View>
<View className="flex-row items-center gap-2">
<View className="bg-indigo-500/10 border border-indigo-500/20 px-2.5 py-1 rounded-xl">
<Text className="text-indigo-300 font-bold text-xs">
{item.calories} kcal
</Text>
</View>
{/* 👈 Delete action button: w-7 h-7 (28px square), bg-red-500/10, border-red-500/20, active:bg-red-500/20 */}
<Pressable
onPress={() => handleDeleteMeal(item.id)}
className="w-7 h-7 rounded-lg bg-red-500/10 border border-red-500/20 items-center justify-center active:bg-red-500/20"
>
<Text className="text-red-400 text-xs font-bold">✕</Text>
</Pressable>
</View>
</View>
)}
/>
</SafeAreaView>
);
}
Test your understanding of safe areas, inputs, and virtualization with these 4 interactive challenges:
SafeAreaView?onChangeText in React Native differ from Web onChange?FlatList significantly faster than ScrollView for 500 items?TextInput ensures the number keypad opens on mobile?Now let's apply this in your real app workspace!
Carbs (g)) to the form and display it in each meal card.💬 Ask Your Teacher: What questions do you have about FlatList performance or TextInput keyboard handling? Let's discuss in the chat!