Agar aapne pehle kabhi Zustand ya Global State use nahi kiya, toh yeh lesson aapke liye hai. Bilkul zero se simple real-world mental model ke sath.
Altamash, tension bilkul mat lo! Zustand koi rocket science nahi hai. Chalo ise ekdum daily life ke example se samjhte hain:
Jab aap React me const [count, setCount] = useState(0) likhte ho, toh count ek chhota sa memory box hai. Jab setCount chalta hai, screen par number badalta hai. Isi memory box ko State bolte hain.
useState doosri screen ko nahi dikhta!Socho aapke ghar ke ek kamre (Today Screen) me ek fridge hai. Doosre kamre (Add Meal Modal) me baithe insaan ko us fridge ka khana nahi dikhega. Jab Add Meal screen par naya meal save hota hai, toh Today screen ko pata hi nahi chalta kyunki dono screens alag-alag hain!
Zustand ek aisi central jagah hai jo sabhi screens ke bahar open hall me rakhi hui hai. Add Meal Modal us board par likhta hai ("Naya meal add hua!"), aur Today Screen, Analytics Screen, aur Bottom Tab Badge teenon us board ko live dekh kar instantly update ho jaate hain!
Board par 100 cheezein likhi ho sakti hain. Bottom Tab ko sirf "Total kitne meals hain" (meals.length) janna hai. Toh wo poora board nahi mangta, sirf ek chhota sa tukda (Selector) leta hai taaki phone ka processor fast rahe.
1. Samjhenge ki useState kahan fail hota hai aur Zustand ki zaroorat kyun padi.
2. Zustand ka sabse aasan 5-line ka Store banana seekhenge (create aur set).
3. Selector se kisi bhi screen me data padhna (Read) aur action se update karna (Write).
4. MacroZone app me Add Meal modal se save karne par Today, Analytics, aur Tab Badges ko live update hote dekhenge!
Zustand ko samajhne ke liye ek Restaurant ka example dekhiye:
Local state sirf usi screen ke andar rehti hai jahan define ki gayi ho.
meals: [ ... ]
Waiter ne board par likha → Sabhi screens instantly update ho gayi!
Zustand me store banana sirf 3 cheezon ka khel hai:
(1) Data kya hai?, (2) Action (Function) kya karega?, aur (3) set() se update kaise karein.
// 1. Zustand se 'create' function import karte hain (No Provider needed!)
import { create } from "zustand";
// 2. TypeScript ko batate hain humare store ka roop (Type Definition)
interface CounterStore {
count: number; // 👈 (1) Data: Kitna count hai
increment: () => void; // 👈 (2) Action: Ek badhane ka function
reset: () => void; // 👈 (2) Action: Zero karne ka function
}
// 3. create() function se Store banate hain
export const useCounterStore = create<CounterStore>()((set) => ({
count: 0, // 👈 Initial value: Shuruat me 0 hoga
// 👈 (3) set() se hum store ki value ko update karte hain
increment: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
}));
| Code Ki Line | Iska Asal Matlab Kya Hai? (Simple Bhasha Me) |
|---|---|
import { create } from "zustand" |
Zustand library se create naam ka factory tool manga liya jo humara shared memory box banayega. |
interface CounterStore { ... } |
TypeScript ko pehle se bata diya ki is memory box me ek number count hoga aur increment naam ka function hoga. |
export const useCounterStore = create... |
Humne ek Custom React Hook bana diya jiska naam useCounterStore hai. Ise koi bhi screen direct import kar sakti hai! |
count: 0 |
App start hote hi count ki default value 0 set kar di. |
set((state) => ({ count: state.count + 1 })) |
set() ek special function hai jo Zustand hume deta hai. Yeh purane state.count me +1 jod kar naya count store me daal deta hai. |
Ab dekho kitna asaan hai! Kisi bhi screen me jao aur 1 line me data uthao ya button click pe action chalao:
import { View, Text, Pressable } from "react-native";
// 1. Apna store import karo
import { useCounterStore } from "../store/useCounterStore";
export default function HomeScreen() {
// 👈 SELECTOR: Sirf 'count' chahiye, toh wahi uthaya
const count = useCounterStore((state) => state.count);
// 👈 ACTION: increment function uthaya
const increment = useCounterStore((state) => state.increment);
return (
// bg-slate-900: Dark background (#0f172a) | p-6: 24px padding (6 * 4px)
<View className="flex-1 bg-slate-900 items-center justify-center p-6">
{/* text-white: White text | text-3xl: 30px font size | font-bold: Bold text */}
<Text className="text-white text-3xl font-bold mb-4">
Count: {count}
</Text>
{/* bg-emerald-500: Vibrant green | py-3 px-6: 12px vertical, 24px horizontal padding | rounded-2xl: 16px radius */}
<Pressable
onPress={increment}
className="bg-emerald-500 py-3 px-6 rounded-2xl active:bg-emerald-600"
>
<Text className="text-slate-950 font-bold text-base">
+1 Badhao
</Text>
</Pressable>
</View>
);
}
💡 Selector ka Secret: useCounterStore((state) => state.count) likhne ka fayda yeh hai ki yeh screen sirf aur sirf tabhi re-render hogi jab count badlega. Agar store me koi doosri cheez (jaise userName) badalti hai, toh yeh screen bina wajah refresh nahi hogi!
Humare store-driven screens me use hone wali har Tailwind class ka exact breakdown:
p-5 = 20px padding (5 × 4px = 20px).gap-3 = 12px gap between flex items (3 × 4px = 12px).rounded-2xl = 16px border-radius (modern curved corners).rounded-3xl = 24px border-radius (large hero cards).rounded-full = 9999px pill shape (progress bars aur circular badges ke liye).h-3 w-full = 12px height progress track, poori width ke sath.bg-slate-950 = Deepest pure dark background (#020617).bg-slate-900 = Elevated card surface (#0f172a).border-slate-800 = Subtle border divider (#1e293b).text-emerald-400 = Bright green text for protein/success values (#34d399).text-sky-400 = Electric sky blue text for calories (#38bdf8).active:bg-emerald-700 = Button tap hone par dark green feedback.Altamash, yahan MacroZone ke saare 6 files ka 100% poora code line-by-line comments aur explanation table ke sath diya gaya hai.
import { create } from "zustand"; // 👈 Store factory — returns a React hook, no Provider needed
// 👈 Domain type shared by Today, Analytics, Add Meal, and Meal Details
export interface MealItem {
id: string;
name: string;
calories: number;
protein: number;
time: string;
}
interface MealStore {
meals: MealItem[];
addMeal: (input: { name: string; calories: number; protein: number }) => void;
removeMeal: (id: string) => void;
}
const seedMeals: MealItem[] = [
{ id: "1", name: "Oats & Whey Protein", calories: 420, protein: 35, time: "8:30 AM" },
{ id: "2", name: "Grilled Chicken Salad", calories: 510, protein: 48, time: "1:15 PM" },
{ id: "3", name: "Boiled Eggs & Almonds", calories: 280, protein: 18, time: "5:00 PM" },
];
// 👈 Curried TypeScript form: create<MealStore>()((set) => ...) so TS infers set() correctly
export const useMealStore = create<MealStore>()((set) => ({
meals: seedMeals,
// 👈 set() merges one level — we return a new meals array (immutable update)
addMeal: (input) =>
set((state) => ({
meals: [
{
id: Date.now().toString(),
name: input.name.trim(),
calories: input.calories,
protein: input.protein,
time: "Just now",
},
...state.meals, // 👈 Spread operator se purane meals retain kiye
],
})),
// 👈 ID match karke delete karne ka function
removeMeal: (id) =>
set((state) => ({
meals: state.meals.filter((meal) => meal.id !== id),
})),
}));
| store/useMealStore.ts Ka Part | Iska Role & Kaam |
|---|---|
create<MealStore>()((set) => ...) |
Zustand se global store banaya jise koi bhi screen direct import kar sakti hai. |
meals: seedMeals |
Default 3 meals diye taaki app shuru me khali na lage. |
addMeal(input) |
Naya meal array ke upar (shuruat me) jodta hai. |
removeMeal(id) |
Array me se specific ID wale meal ko filter karke hata deta hai. |
import { useState } from "react";
import { View, Text, TextInput, Pressable, FlatList } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; // 👈 Handles notch insets
import { useRouter, Link } from "expo-router"; // 👈 Router hooks
import { Ionicons } from "@expo/vector-icons"; // 👈 Vector icons
import { useMealStore } from "../../store/useMealStore"; // 👈 Shared meal store
export default function TodayScreen() {
const router = useRouter(); // 👈 Navigation controller instance
// 👈 Selectors: this screen re-renders only when meals / actions it uses change
const meals = useMealStore((state) => state.meals);
const addMeal = useMealStore((state) => state.addMeal);
const removeMeal = useMealStore((state) => state.removeMeal);
// 👈 Form fields stay local — they are draft UI, not shared app data
const [name, setName] = useState("");
const [calories, setCalories] = useState("");
const [protein, setProtein] = useState("");
// 👈 Derived values from the same store (Analytics tab sees the same totals)
const totalCalories = meals.reduce((sum, m) => sum + m.calories, 0);
const totalProtein = meals.reduce((sum, m) => sum + m.protein, 0);
const calorieTarget = 2400;
const handleAddMeal = () => {
if (!name.trim() || !calories.trim()) return;
addMeal({
name: name.trim(),
calories: parseInt(calories) || 0,
protein: parseInt(protein) || 0,
});
setName("");
setCalories("");
setProtein("");
};
return (
// 👈 flex-1 bg-slate-950 px-5 pt-2: Full screen dark canvas with safe horizontal padding
<SafeAreaView className="flex-1 bg-slate-950 px-5 pt-2">
{/* ── Top Dashboard Header & Progress ── */}
{/* 👈 bg-slate-900 border border-slate-800 rounded-3xl p-5 mb-4: Elevated card container */}
<View className="bg-slate-900 border border-slate-800 rounded-3xl p-5 mb-4 shadow-2xl">
<View className="flex-row items-center justify-between mb-3">
<View>
<Text className="text-xs font-bold uppercase tracking-wider text-emerald-400">
Daily Fuel
</Text>
<Text className="text-2xl font-bold text-white">Today's Meals</Text>
</View>
{/* 👈 Declarative Link to Modal Sheet (slides over tabs) */}
<Link href="/add-meal" asChild>
<Pressable className="bg-emerald-600 active:bg-emerald-700 px-3.5 py-2 rounded-2xl flex-row items-center gap-1.5">
<Ionicons name="add-circle" size={16} color="#fff" />
<Text className="text-white text-xs font-bold">Modal</Text>
</Pressable>
</Link>
</View>
{/* 👈 Metric cards grid: flex-row gap-3 */}
<View className="flex-row gap-3">
<View className="flex-1 bg-slate-950/80 border border-slate-800 p-3 rounded-2xl">
<Text className="text-slate-400 text-xs">Calories Eaten</Text>
<Text className="text-white text-xl font-bold mt-1">
{totalCalories}{" "}
<Text className="text-xs text-slate-500 font-normal">/ {calorieTarget}</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-emerald-400 text-xl font-bold mt-1">{totalProtein}g</Text>
</View>
</View>
</View>
{/* ── Quick Add Form ── */}
{/* 👈 bg-slate-900/90 border border-slate-800 p-4 rounded-2xl: Dark form wrapper */}
<View className="bg-slate-900/90 border border-slate-800 p-4 rounded-2xl mb-4 gap-2.5">
<TextInput
value={name}
onChangeText={setName}
placeholder="Quick Food Name (e.g. Protein Shake)"
placeholderTextColor="#64748b"
className="bg-slate-950 border border-slate-800 text-white px-3.5 py-2.5 rounded-xl text-sm"
/>
<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>
<Pressable
onPress={handleAddMeal}
className="bg-emerald-600 active:bg-emerald-700 py-3 rounded-xl items-center mt-1"
>
<Text className="text-white font-semibold text-sm">+ Quick Log Meal</Text>
</Pressable>
</View>
{/* ── Clickable Meal FlatList ── */}
<FlatList
data={meals}
keyExtractor={(item) => item.id}
showsVerticalScrollIndicator={false}
renderItem={({ item }) => (
<Pressable
onPress={() =>
router.push({
pathname: "/meal/[id]",
params: { id: item.id },
})
}
className="bg-slate-900 border border-slate-800/80 p-3.5 rounded-2xl flex-row justify-between items-center mb-2.5 active:bg-slate-800"
>
<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">
{item.time} • {item.protein}g protein
</Text>
</View>
<View className="flex-row items-center gap-2">
<View className="bg-emerald-500/10 border border-emerald-500/20 px-2.5 py-1 rounded-xl">
<Text className="text-emerald-300 font-bold text-xs">{item.calories} kcal</Text>
</View>
<Pressable
onPress={(e) => {
e.stopPropagation?.();
removeMeal(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>
</Pressable>
)}
/>
</SafeAreaView>
);
}
| app/(tabs)/index.tsx Ka Part | Iska Role & Kaam |
|---|---|
useMealStore((s) => s.meals) |
Store se meals array padhta hai aur FlatList ko render karta hai. |
totalCalories = meals.reduce(...) |
Derived calculation: store se aayi meals ka sum dynamically karta hai. |
removeMeal(item.id) |
Item ke ✕ button par tap karne par store se meal delete karta hai. |
import { useState } from "react";
import { View, Text, TextInput, Pressable } from "react-native";
import { useRouter } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import { useMealStore } from "../store/useMealStore";
export default function AddMealModal() {
const router = useRouter();
// 👈 Selector se sirf addMeal action uthaya
const addMeal = useMealStore((state) => state.addMeal);
// 👈 Local typing draft state
const [name, setName] = useState("");
const [calories, setCalories] = useState("");
const [protein, setProtein] = useState("");
const [error, setError] = useState("");
const handleSave = () => {
if (!name.trim() || !calories.trim()) {
setError("Please enter at least meal name and calories.");
return;
}
// 👈 1. Store me naya meal add kiya
addMeal({
name: name.trim(),
calories: parseInt(calories) || 0,
protein: parseInt(protein) || 0,
});
// 👈 2. Modal close kiya — Today aur Analytics khud update ho jayenge
router.back();
};
return (
<SafeAreaView className="flex-1 bg-slate-950 p-6 justify-between">
<View className="gap-4">
<View className="flex-row justify-between items-center mb-2">
<Text className="text-2xl font-bold text-white">Log New Meal</Text>
<Pressable onPress={() => router.back()} className="px-3 py-1.5 bg-slate-900 rounded-xl">
<Text className="text-slate-400 font-medium">Cancel</Text>
</Pressable>
</View>
{error ? <Text className="text-red-400 text-xs">{error}</Text> : null}
<TextInput
value={name}
onChangeText={(t) => { setName(t); setError(""); }}
placeholder="Food Name (e.g., Paneer Rice Bowl)"
placeholderTextColor="#64748b"
className="bg-slate-900 border border-slate-800 text-white p-4 rounded-2xl"
/>
<TextInput
value={calories}
onChangeText={(t) => { setCalories(t); setError(""); }}
placeholder="Calories (kcal)"
placeholderTextColor="#64748b"
keyboardType="numeric"
className="bg-slate-900 border border-slate-800 text-white p-4 rounded-2xl"
/>
<TextInput
value={protein}
onChangeText={setProtein}
placeholder="Protein (grams)"
placeholderTextColor="#64748b"
keyboardType="numeric"
className="bg-slate-900 border border-slate-800 text-white p-4 rounded-2xl"
/>
</View>
<Pressable onPress={handleSave} className="bg-emerald-500 active:bg-emerald-600 py-4 rounded-2xl items-center">
<Text className="text-slate-950 font-bold text-base">Save to Shared Store</Text>
</Pressable>
</SafeAreaView>
);
}
| app/add-meal.tsx Ka Part | Iska Role & Kaam |
|---|---|
useMealStore((s) => s.addMeal) |
Modal me sirf write action mangwaya (Data read karne ki zaroorat nahi). |
handleSave() |
Form validate karta hai, store me meal bhejta hai aur router.back() se modal band karta hai. |
import { View, Text, ScrollView } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; // 👈 Hardware safe area insets
import { Ionicons } from "@expo/vector-icons"; // 👈 Vector icons
import { useMealStore } from "../../store/useMealStore"; // 👈 Live totals from shared meals
export default function AnalyticsScreen() {
// 👈 Derived selectors: totals recompute when meals change — no duplicate local state
const caloriesConsumed = useMealStore((state) =>
state.meals.reduce((sum, meal) => sum + meal.calories, 0)
);
const proteinCurrent = useMealStore((state) =>
state.meals.reduce((sum, meal) => sum + meal.protein, 0)
);
const calorieGoal = 2400;
const proteinGoal = 160;
const carbsGoal = 220;
const fatsGoal = 70;
// 👈 Lightweight estimates until meals store full macros (Lesson 06 persist/goals)
const carbsCurrent = Math.max(0, Math.round(caloriesConsumed * 0.12));
const fatsCurrent = Math.max(
0,
Math.round((caloriesConsumed - proteinCurrent * 4 - carbsCurrent * 4) / 9)
);
const caloriePct = calorieGoal === 0 ? 0 : (caloriesConsumed / calorieGoal) * 100;
const proteinPct = proteinGoal === 0 ? 0 : (proteinCurrent / proteinGoal) * 100;
const carbsPct = carbsGoal === 0 ? 0 : (carbsCurrent / carbsGoal) * 100;
const fatsPct = fatsGoal === 0 ? 0 : (fatsCurrent / fatsGoal) * 100;
return (
// 👈 flex-1 bg-slate-950 px-5 pt-2: Dark root container
<SafeAreaView className="flex-1 bg-slate-950 px-5 pt-2">
<ScrollView showsVerticalScrollIndicator={false}>
<View className="mb-4">
<Text className="text-xs font-bold uppercase tracking-wider text-sky-400">
Performance & Nutrition
</Text>
<Text className="text-2xl font-bold text-white">Daily Analytics</Text>
</View>
<View className="bg-slate-900 border border-slate-800 rounded-3xl p-5 mb-4 shadow-2xl">
<View className="flex-row justify-between items-center mb-2">
<Text className="text-slate-300 font-semibold">Energy Balance</Text>
<Text className="text-sky-400 font-bold text-sm">
{Math.round(caloriePct)}% of goal
</Text>
</View>
<View className="h-3 w-full bg-slate-950 rounded-full overflow-hidden mb-3 border border-slate-800">
<View
className="h-full bg-sky-500 rounded-full"
style={{ width: `${Math.min(100, caloriePct)}%` }}
/>
</View>
<View className="flex-row justify-between">
<Text className="text-slate-400 text-xs">{caloriesConsumed} kcal eaten</Text>
<Text className="text-slate-400 text-xs">
{Math.max(0, calorieGoal - caloriesConsumed)} kcal remaining
</Text>
</View>
</View>
<Text className="text-sm font-bold text-slate-300 uppercase tracking-wider mb-3">
Macronutrient Split
</Text>
<View className="bg-slate-900 border border-slate-800 rounded-2xl p-4 mb-3 border-l-4 border-l-emerald-500">
<View className="flex-row justify-between items-center mb-1.5">
<Text className="text-white font-bold text-sm">🍗 Protein</Text>
<Text className="text-emerald-400 font-bold text-sm">
{proteinCurrent}g / {proteinGoal}g
</Text>
</View>
<View className="h-2 w-full bg-slate-950 rounded-full overflow-hidden">
<View
className="h-full bg-emerald-500 rounded-full"
style={{ width: `${Math.min(100, proteinPct)}%` }}
/>
</View>
</View>
<View className="bg-slate-900 border border-slate-800 rounded-2xl p-4 mb-3 border-l-4 border-l-amber-500">
<View className="flex-row justify-between items-center mb-1.5">
<Text className="text-white font-bold text-sm">🌾 Carbohydrates</Text>
<Text className="text-amber-400 font-bold text-sm">
{carbsCurrent}g / {carbsGoal}g
</Text>
</View>
<View className="h-2 w-full bg-slate-950 rounded-full overflow-hidden">
<View
className="h-full bg-amber-500 rounded-full"
style={{ width: `${Math.min(100, carbsPct)}%` }}
/>
</View>
</View>
<View className="bg-slate-900 border border-slate-800 rounded-2xl p-4 mb-4 border-l-4 border-l-rose-500">
<View className="flex-row justify-between items-center mb-1.5">
<Text className="text-white font-bold text-sm">🥑 Healthy Fats</Text>
<Text className="text-rose-400 font-bold text-sm">
{fatsCurrent}g / {fatsGoal}g
</Text>
</View>
<View className="h-2 w-full bg-slate-950 rounded-full overflow-hidden">
<View
className="h-full bg-rose-500 rounded-full"
style={{ width: `${Math.min(100, fatsPct)}%` }}
/>
</View>
</View>
<View className="bg-slate-900 border border-slate-800 p-4 rounded-2xl flex-row items-center gap-3 mb-6">
<View className="w-10 h-10 rounded-xl bg-amber-500/10 border border-amber-500/20 items-center justify-center">
<Ionicons name="flame" size={22} color="#f59e0b" />
</View>
<View>
<Text className="text-white font-bold text-sm">Live store connected 🔥</Text>
<Text className="text-slate-400 text-xs">
Log a meal on Today — this chart updates without passing props.
</Text>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}
| app/(tabs)/analytics.tsx Ka Part | Iska Role & Kaam |
|---|---|
reduce((sum, m) => sum + m.calories) |
Store se aayi saari meals ko dynamically jod kar live progress percentage nikalta hai. |
style={{ width: `${caloriePct}%` }} |
Progress bar ko visual fill deta hai (e.g. 50% = half bar). |
import { Tabs } from "expo-router"; // 👈 Bottom Tabs Navigator component
import { Ionicons } from "@expo/vector-icons"; // 👈 Crisp cross-platform vector icons
import { useMealStore } from "../../store/useMealStore"; // 👈 Live badge count from meals
export default function TabLayout() {
// 👈 Selector: Sirf meals ki length subscribe ki (Badge ke liye)
const mealCount = useMealStore((state) => state.meals.length);
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: "#10b981", // 👈 Emerald-500: active highlight
tabBarInactiveTintColor: "#64748b", // 👈 Slate-500: muted unselected color
tabBarStyle: {
backgroundColor: "#0f172a", // 👈 bg-slate-900: dark surface
borderTopColor: "#1e293b", // 👈 border-slate-800 divider
height: 62,
paddingBottom: 8,
paddingTop: 6,
},
tabBarLabelStyle: {
fontSize: 11,
fontWeight: "600",
},
headerStyle: { backgroundColor: "#020617" },
headerTintColor: "#f8fafc",
}}
>
{/* Tab 1: Today Screen */}
<Tabs.Screen
name="index"
options={{
title: "Today",
headerShown: false,
tabBarIcon: ({ color, focused }) => (
<Ionicons
name={focused ? "calendar" : "calendar-outline"}
size={22}
color={color}
/>
),
tabBarBadge: mealCount > 0 ? mealCount : undefined, // 👈 Live badge count
tabBarBadgeStyle: {
backgroundColor: "#10b981",
color: "#020617",
fontWeight: "bold",
fontSize: 10,
},
}}
/>
{/* Tab 2: Analytics Screen */}
<Tabs.Screen
name="analytics"
options={{
title: "Analytics",
headerShown: false,
tabBarIcon: ({ color, focused }) => (
<Ionicons
name={focused ? "stats-chart" : "stats-chart-outline"}
size={22}
color={color}
/>
),
}}
/>
{/* Tab 3: Goals Screen */}
<Tabs.Screen
name="goals"
options={{
title: "Goals",
headerShown: false,
tabBarIcon: ({ color, focused }) => (
<Ionicons
name={focused ? "flag" : "flag-outline"}
size={22}
color={color}
/>
),
}}
/>
</Tabs>
);
}
| app/(tabs)/_layout.tsx Ka Part | Iska Role & Kaam |
|---|---|
useMealStore((s) => s.meals.length) |
Store se total meals count nikalta hai. |
tabBarBadge: mealCount > 0 ? mealCount : undefined |
Jab tak 0 se zyada meals hain, tab par live count badge dikhata hai. |
import { useLocalSearchParams, useRouter } from "expo-router"; // 👈 Router hooks
import { View, Text, Pressable } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { useMealStore } from "../../store/useMealStore"; // 👈 Lookup meal by id from store
export default function MealDetailScreen() {
const router = useRouter();
const { id } = useLocalSearchParams<{ id: string }>(); // 👈 URL se id li
// 👈 Selector finds one meal from store by id
const meal = useMealStore((state) => state.meals.find((item) => item.id === id));
const calNum = meal?.calories ?? 0;
const proNum = meal?.protein ?? 0;
const carbsNum = Math.max(0, Math.round(calNum * 0.12));
const fatsNum = Math.max(0, Math.round((calNum - proNum * 4 - carbsNum * 4) / 9));
const totalMacroCals = proNum * 4 + carbsNum * 4 + fatsNum * 9 || 1;
const proPercent = Math.min(100, Math.round(((proNum * 4) / totalMacroCals) * 100));
const carbsPercent = Math.min(100 - proPercent, Math.round(((carbsNum * 4) / totalMacroCals) * 100));
const fatsPercent = Math.max(0, 100 - proPercent - carbsPercent);
return (
<SafeAreaView className="flex-1 bg-slate-950 px-5 pt-2">
<View className="flex-row items-center justify-between mb-6">
<Pressable
onPress={() => router.back()}
className="bg-slate-900 border border-slate-800 px-4 py-2.5 rounded-2xl active:bg-slate-800 flex-row items-center gap-1.5"
>
<Text className="text-emerald-400 font-bold text-sm">← Back</Text>
</Pressable>
<View className="bg-slate-900 border border-slate-800 px-3 py-1.5 rounded-xl">
<Text className="text-slate-400 text-xs font-mono">ID: #{id || "0"}</Text>
</View>
</View>
<View className="bg-slate-900 border border-slate-800 p-6 rounded-3xl mb-5 shadow-2xl">
<View className="flex-row items-center justify-between mb-2">
<Text className="text-xs font-bold text-emerald-400 uppercase tracking-wider">
Meal Breakdown
</Text>
<Text className="text-2xl">🍽️</Text>
</View>
<Text className="text-2xl font-extrabold text-white mb-4">
{meal?.name || "Meal not found"}
</Text>
<View className="flex-row gap-3 mb-5">
<View className="flex-1 bg-slate-950/80 border border-slate-800 p-4 rounded-2xl">
<Text className="text-slate-400 text-xs uppercase tracking-wide">Energy</Text>
<Text className="text-white text-2xl font-bold mt-1">
{calNum} <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-4 rounded-2xl">
<Text className="text-slate-400 text-xs uppercase tracking-wide">Protein</Text>
<Text className="text-emerald-400 text-2xl font-bold mt-1">
{proNum} <Text className="text-xs text-emerald-500 font-normal">g</Text>
</Text>
</View>
</View>
<Text className="text-slate-400 text-xs font-semibold mb-2">
Caloric Distribution Breakdown
</Text>
<View className="h-3 w-full bg-slate-950 rounded-full overflow-hidden flex-row mb-3">
<View style={{ width: `${proPercent}%` }} className="h-full bg-emerald-500" />
<View style={{ width: `${carbsPercent}%` }} className="h-full bg-amber-500" />
<View style={{ width: `${fatsPercent}%` }} className="h-full bg-rose-500" />
</View>
</View>
<Pressable
onPress={() => router.back()}
className="bg-emerald-600 active:bg-emerald-700 py-3.5 rounded-2xl items-center"
>
<Text className="text-white font-bold text-sm">
Return to Dashboard
</Text>
</Pressable>
</SafeAreaView>
);
}
| app/meal/[id].tsx Ka Part | Iska Role & Kaam |
|---|---|
useLocalSearchParams() |
URL se clicked meal ka id param nikalta hai. |
meals.find(item => item.id === id) |
Store me se matching ID wale meal ka poora data nikal kar card me render karta hai. |
Chalo test karte hain concepts clear hue ya nahi. Options par tap karein:
useMealStore((state) => state.meals) likhte hain, toh ise kya kehte hain?set() function kya karta hai?MacroZone app aapke phone (Expo Go) par chal raha hai. Yeh 3 steps karke dekhein:
💬 Ask Your Teacher: Agar koi bhi word ya code line abhi bhi samajh na aaye, toh bina jhijhak chat me likhiye: "Sir, mujhe yeh line samajh nahi aayi". Hum live milkar solve karenge!