I new to RN and I am trying to use expo router in my app. But I experience an issue with the structure of the files. Right now the structure is like this:
File structure
I use the (app) for authentication. But bsically the index.tsx is my Home Page, which contains event cards like this:
Home page
I need to add a dynamic route so that when a user presses a card it is redirected to the details of this event. And it should be able to navigate back and have the tab below. But I am having issues how to structure the files.
This is my
(app)/_layout.tsx
import {Text} from 'react-native';
import {Redirect, Tabs} from 'expo-router';
import {useAuth} from "@/app/context/context";
import React from "react";
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
export default function AppLayout() {
const { user, isLoading } = useAuth();
const colorScheme = useColorScheme();
// You can keep the splash screen open, or render a loading screen like we do here.
if (isLoading) {
return <Text>Loading...</Text>;
}
// Only require authentication within the (app) group's layout as users
// need to be able to access the (auth) group and sign in again.
if (!user) {
// On web, static rendering will stop here as the user is not authenticated
// in the headless Node process that the pages are rendered in.
return <Redirect href="/sign-in" />;
}
// This layout can be deferred because it's not the root layout.
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
}}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? 'home' : 'home-outline'} color={color} />
),
}}
/>
<Tabs.Screen
name="create-event"
options={{
title: 'Create',
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? 'add' : 'add-outline'} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? 'code-slash' : 'code-slash-outline'} color={color} />
),
}}
/>
</Tabs>
);
}
This is my index.tsx, which uses EventCard to navigate:
import React, {useCallback, useState} from 'react';
import {View, Text, FlatList, StyleSheet, ActivityIndicator} from 'react-native';
import {FIRESTORE_DB} from "@/firebaseConfig";
import { collection, getDocs } from 'firebase/firestore';
import { useFocusEffect } from '@react-navigation/native';
import EventCard from "../../components/EventCard";
// Event type definition
interface Event {
id: string;
title: string;
date: string;
time: string;
location: string;
availablePlaces: number;
userId: string;
}
// Main component to render the list of events
const Events = () => {
const [events, setEvents] = useState<Event[]>([]);
const [loading, setLoading] = useState<boolean>(true);
// Fetch events from Firestore
const fetchEvents = async () => {
try {
const eventsCollection = collection(FIRESTORE_DB, 'events');
const eventSnapshot = await getDocs(eventsCollection);
const eventList: Event[] = eventSnapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
})) as Event[];
setEvents(eventList);
console.log("Index: ", eventList);
} catch (error) {
console.error("Error fetching events: ", error);
} finally {
setLoading(false);
}
};
useFocusEffect(
useCallback(() => {
fetchEvents();
}, [])
);
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#0000ff" />
<Text>Loading Events...</Text>
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={events}
renderItem={({ item }) => <EventCard event={item} />}
keyExtractor={(item) => item.id}
showsVerticalScrollIndicator={false}
/>
</View>
);
};
export default Events;
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#f0f0f0',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
card: {
backgroundColor: '#fff',
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 6,
elevation: 5,
marginBottom: 16,
overflow: 'hidden',
},
image: {
width: '100%',
height: 150,
},
cardContent: {
padding: 12,
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
marginBottom: 8,
},
time: {
fontSize: 14,
color: '#38A169', // Greenish text for time
marginBottom: 4,
},
location: {
fontSize: 14,
color: '#888', // Light gray for location
},
});
And this is the EventCard component:
import React from 'react';
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
import { useRouter } from 'expo-router';
// Event type definition
interface Event {
id: string;
title: string;
date: string;
time: string;
location: string;
availablePlaces: number;
userId: string;
}
// Card component to display event details
const EventCard: React.FC<{ event: Event }> = ({ event }) => {
const router = useRouter();
// Combine the date and time into a single Date object
const eventDateTime = new Date(`${event.date}T${event.time}`);
// Format the time and day
const formattedTime = eventDateTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const formattedDay = eventDateTime.toLocaleDateString([], { weekday: 'long' });
return (
<TouchableOpacity onPress={() => router.push(`/event/${event.id}`)}>
<View style={styles.card}>
<Image
source={{
uri: 'https://images.unsplash.com/photo-1612534847738-b3af9bc31f0c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
}} // Replace with dynamic event image URL
style={styles.image}
/>
<View style={styles.cardContent}>
<Text style={styles.title}>{event.title}</Text>
<Text style={styles.time}>
{formattedTime} / {formattedDay}
</Text>
<Text style={styles.location}>{event.location}</Text>
</View>
</View>
</TouchableOpacity>
);
};
export default EventCard;
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 6,
elevation: 5,
marginBottom: 16,
overflow: 'hidden',
},
image: {
width: '100%',
height: 150,
},
cardContent: {
padding: 12,
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
marginBottom: 8,
},
time: {
fontSize: 14,
color: '#38A169', // Greenish text for time
marginBottom: 4,
},
location: {
fontSize: 14,
color: '#888', // Light gray for location
},
});
I want to be able to have both Stack and Tab navigation in the home page.
Lachezar Mitov is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.