
Create a mobile app to explore a movie catalog, view details, and navigate between categories, practicing navigation with React Navigation using Bottom Tabs and Native Stack.
1https://github.com/breatheco-de/react-native-cli-hello
1npm install
1cd ios 2bundle install # optional if you use Bundler 3bundle exec pod install || pod install 4cd ..
1npx react-native start --reset-cache
1npm run android
1npm run ios
1npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs 2npm install react-native-screens react-native-safe-area-context
1import { NavigationContainer } from '@react-navigation/native'; 2import TabsNavigator from './src/navigation/TabsNavigator'; 3 4export default function App() { 5 return ( 6 <NavigationContainer> 7 <TabsNavigator /> 8 </NavigationContainer> 9 ); 10}
1├─ src/ 2│ ├─ navigation/ 3│ │ ├─ TabsNavigator.tsx # Main tabs 4│ │ └─ StackNavigator.tsx # Stack for details and internal routes 5│ ├─ screens/ 6│ │ ├─ HomeScreen.tsx # Movie list 7│ │ ├─ MovieDetailScreen.tsx # Detail (receives typed params) 8│ │ ├─ CategoriesScreen.tsx # Category list 9│ │ └─ CategoryMoviesScreen.tsx # Movies filtered by category 10│ ├─ components/ 11│ │ ├─ MovieCard.tsx 12│ │ └─ Grid.tsx 13│ ├─ data/ 14│ │ └─ movies.ts # Local mock (no API) 15│ └─ types/ 16│ └─ index.ts # Shared types (Movie, Route params, etc.) 17├─ App.tsx 18├─ package.json 19└─ tsconfig.json
TabsNavigator.tsx for the Home, Categories, and Favorites tabs.MovieDetailScreen.Create the file src/data/movies.ts with local data:
1export type Movie = { 2 id: number; 3 title: string; 4 year: number; 5 categories: string[]; 6 rating: number; // 0–10 7}; 8 9export const movies: Movie[] = [ 10 { id: 1, title: 'Inception', year: 2010, categories: ['Sci-Fi', 'Action'], rating: 8.8 }, 11 { id: 2, title: 'Interstellar', year: 2014, categories: ['Sci-Fi', 'Drama'], rating: 8.6 }, 12 // ... 13];
Use typed routes/params in the Stack:
RootStackParamList) in src/types/index.ts.id and retrieve it in MovieDetailScreen.Do not use fetch or external APIs: everything comes from movies.ts.
src/navigation/StackNavigator.tsx
1import { createNativeStackNavigator } from '@react-navigation/native-stack'; 2import HomeScreen from '../screens/HomeScreen'; 3import MovieDetailScreen from '../screens/MovieDetailScreen'; 4import { RootStackParamList } from '../types'; 5 6const Stack = createNativeStackNavigator<RootStackParamList>(); 7 8export default function StackNavigator() { 9 return ( 10 <Stack.Navigator> 11 <Stack.Screen 12 name="Home" 13 component={HomeScreen} 14 options={{ title: 'Movies' }} 15 /> 16 <Stack.Screen 17 name="MovieDetail" 18 component={MovieDetailScreen} 19 options={({ route }) => ({ title: route.params?.title ?? 'Detail' })} 20 /> 21 </Stack.Navigator> 22 ); 23}
src/navigation/TabsNavigator.tsx
1import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; 2import StackNavigator from './StackNavigator'; 3import CategoriesScreen from '../screens/CategoriesScreen'; 4import FavoritesScreen from '../screens/FavoritesScreen'; 5 6type TabsParamList = { 7 Feed: undefined; // Will contain the Stack (Home + Detail) 8 Categories: undefined; 9 Favorites: undefined; 10}; 11 12const Tab = createBottomTabNavigator<TabsParamList>(); 13 14export default function TabsNavigator() { 15 return ( 16 <Tab.Navigator> 17 <Tab.Screen 18 name="Feed" 19 component={StackNavigator} 20 options={{ title: 'Home' }} 21 /> 22 <Tab.Screen 23 name="Categories" 24 component={CategoriesScreen} 25 /> 26 <Tab.Screen 27 name="Favorites" 28 component={FavoritesScreen} 29 /> 30 </Tab.Navigator> 31 ); 32}
src/types/index.ts
1export type RootStackParamList = { 2 Home: undefined; 3 MovieDetail: { id: number; title?: string }; 4};
src/screens/HomeScreen.tsx
1import { View, FlatList, Pressable } from 'react-native'; 2import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3import { RootStackParamList } from '../types'; 4import { movies } from '../data/movies'; 5import MovieCard from '../components/MovieCard'; 6 7type Props = NativeStackScreenProps<RootStackParamList, 'Home'>; 8 9export default function HomeScreen({ navigation }: Props) { 10 return ( 11 <View style={{ flex: 1, padding: 16 }}> 12 <FlatList 13 data={movies} 14 keyExtractor={(m) => String(m.id)} 15 renderItem={({ item }) => ( 16 <Pressable 17 onPress={() => 18 navigation.navigate('MovieDetail', { id: item.id, title: item.title }) 19 } 20 > 21 <MovieCard movie={item} /> 22 </Pressable> 23 )} 24 /> 25 </View> 26 ); 27}
src/screens/MovieDetailScreen.tsx
1import { View, Text } from 'react-native'; 2import { NativeStackScreenProps } from '@react-navigation/native-stack'; 3import { RootStackParamList } from '../types'; 4import { movies } from '../data/movies'; 5 6type Props = NativeStackScreenProps<RootStackParamList, 'MovieDetail'>; 7 8export default function MovieDetailScreen({ route }: Props) { 9 const { id } = route.params; 10 const movie = movies.find((m) => m.id === id); 11 12 if (!movie) return <Text style={{ padding: 16 }}>Movie not found</Text>; 13 14 return ( 15 <View style={{ flex: 1, padding: 16, gap: 6 }}> 16 <Text style={{ fontSize: 22, fontWeight: '600' }}>{movie.title}</Text> 17 <Text>Year: {movie.year}</Text> 18 <Text>Categories: {movie.categories.join(', ')}</Text> 19 <Text>Rating: {movie.rating}</Text> 20 </View> 21 ); 22}
NativeStackScreenProps and a ParamList.options for dynamic headers (e.g., use the movie title in the detail).MovieCard, Grid).number before searching for movies.