I'm building a React Native gig guide app, using stack navigation to get around the different screens.
So here's what I want to happen - the user can navigate from Map.js
to List.js
, where they're presented with a list of gigs. I want them to be able to tap on a gig and be directed to GigDetails.js
, where they can see the details about that specific gig.
Users also have the option of tapping on a gig marker in Map.js
which also directs them to GigDetails.js
. How do I configure my routing so users can get to GigDetails
from both Map.js
and List.js
?
Currently, Map-to-details is working, but I don't know how to go from List-to-details.
App structure is as follows (route with asterisk is the part I'm having trouble with:
screens/List.js <-----> screens/Map.js (Home screen)
*| |
*| | (GigDetails navigated to via map Marker Callout)
*| |
*GigDetails.js GigDetails.js
Code:
App.js
import 'react-native-gesture-handler';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { MyStack } from './routes/homeStack';
export default function App() {
return (
<NavigationContainer>
<MyStack/>
</NavigationContainer>
);
}
HomeStack.js
import { createStackNavigator } from "@react-navigation/stack";
import List from '../screens/List'
import Map from '../screens/Map'
import Header from "../components/Header";
import GigDetails from "../screens/GigDetails";
const Stack = createStackNavigator()
export const MyStack = () => {
return (
<Stack.Navigator
initialRouteName="Map"
>
<Stack.Screen
name="Map"
component={Map}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
<Stack.Screen
name="List"
component={List}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
<Stack.Screen
name="GigDetails"
component={GigDetails}
options={{
headerTitle: () => <Header/>,
headerTitleAlign: 'center'
}}
/>
</Stack.Navigator>
);
};
Map.js
import { StyleSheet,View,Text,Pressable } from 'react-native'
import GigMap from '../components/GigMap'
const Map = ({ navigation }) => {
return (
<View style = {styles.container}>
<GigMap navigation = {navigation}/>
<View style = {styles.footer}>
<Pressable
title = "Go to list view"
onPress = {() => navigation.navigate("List")}
style = {styles.button}
>
<Text style = {styles.buttonText}>List View</Text>
</Pressable>
</View>
</View>
)
}
GigMap.js
import { useState,useEffect } from 'react';
import { StyleSheet, Text, View,Pressable } from 'react-native';
import MapView from 'react-native-maps';
import { Marker,Callout } from 'react-native-maps';
import { query,collection,getDocs } from 'firebase/firestore';
import { db } from '../firebase';
import CalloutView from './CalloutView';
import { mapStyle } from '../util/mapStyle';
import dayjs from 'dayjs';
const GigMap = ({ navigation }) => {
const [gigs, setGigs] = useState([]);
const [ date,setDate ] = useState(dateToday)
const [ daysAdded,setDaysAdded ] = useState(1)
//Generating current date
const addHours =(numOfHours, date = new Date()) => {
date.setTime(date.getTime() numOfHours * 60 * 60 * 1000);
return date;
}
let localDate = addHours(13)
useEffect(() => {
setDate(localDate)
},[])
const addDay = () => {
setDaysAdded(daysAdded 1)
localDate.setDate(localDate.getDate() daysAdded)
setDate(localDate)
}
console.log(date)
const day = new Date().getDate();
const month = new Date().getMonth() 1;
const year = new Date().getFullYear();
const dateToday = `${day}/${month}/${year}`;
//Making call to Firebase to retrieve gig documents from 'gigs' collection
useEffect(() => {
const getGigs = async () => {
try {
const gigArray = [];
const q = query(collection(db, "gigs"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) =>
gigArray.push({ id: doc.id, ...doc.data() })
);
setGigs(gigArray);
} catch (err) {
console.log(`Error: ${err}`);
}
};
getGigs();
}, []);
//Filtering through gigs to return only current day's gigs
const gigsToday = gigs.filter((gig) => gig.date === dateToday);
return (
<View style={styles.container}>
<Text style={styles.headerText}>Today's gigs</Text>
<MapView
initialRegion={{
latitude: -41.29416,
longitude: 174.77782,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}
style={styles.map}
customMapStyle={mapStyle}
>
{gigsToday.map((gig, i) => (
<Marker
key={i}
coordinate={{
latitude: gig.location.latitude,
longitude: gig.location.longitude,
}}
image={require("../assets/Icon_Gold_48x48.png")}
>
<Callout
style={styles.callout}
onPress={() =>
navigation.navigate("GigDetails", {
venue: gig.venue,
date: gig.date,
gigName: gig.gigName,
time: gig.time,
})
}
>
<CalloutView
venue={gig.venue}
date={gig.date}
gigName={gig.gigName}
time={gig.time}
style={styles.calloutView}
/>
</Callout>
</Marker>
))}
</MapView>
<View style = {styles.buttonOptions}>
<Pressable >
<Text style = {styles.buttonOptionsText}>previous day's gigs</Text>
</Pressable>
<Pressable onPress = {addDay}>
<Text style = {styles.buttonOptionsText}>next day's gigs</Text>
</Pressable>
</View>
</View>
);
};
List.js
import { View,Text } from 'react-native'
import ListByDay from '../components/ListByDay';
const List = () => {
return (
<View>
<ListByDay/>
</View>
)
}
export default List;
ListByDay.js
import { StyleSheet, Text, View,FlatList,TouchableOpacity } from 'react-native';
import { useGigs } from '../hooks/useGigs';
const ListByDay = ({ navigation }) => {
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
test: {
borderWidth:1,
borderColor:'black',
},
header: {
padding:10
}
})
export default ListByDay;
CodePudding user response:
The component ListByDay
currently tries to destructure the navigation
object from its props. However, the navigation
object is passed by the react-navigation framework to the components that are defined as screens inside a navigator
. This is not the case for ListByDay
. Thus, you cannot access the navigation
object as you are currently trying to do.
There are multiple options.
1) Pass the navigation object as a prop from its parent which is a screen inside a navigator
Since List
is the parent component for ListByDay
and List
is an actual screen defined in your stack navigator, it will receive the navigation
object as a prop from the react-navigation framework. You can access it and pass it to its children.
const List = ({navigation}) => {
return (
<View>
<ListByDay navigation={navigation} />
</View>
)
}
Then, implement the navigation logic in the onPress
function of your TouchableOpacity
inside ListByDay
.
const ListByDay = ({ navigation }) => {
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test} onPress={() => navigation.navigate(...)}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
2) Use the useNavigation hook
If you do not want to pass the navigation object down to ListByDay
, then you can always use the useNavigation
hook instead.
const ListByDay = () => {
const navigation = useNavigation()
const gigs = useGigs()
return (
<View>
<Text style = {styles.header}>Gigs today</Text>
<FlatList
data = {gigs}
renderItem = {({ item }) => (
<TouchableOpacity style = {styles.test} onPress={() => navigation.navigate(...)}>
<Text>{item.venue}</Text>
<Text>{item.gigName}</Text>
<Text>{item.date}</Text>
<Text>{item.time}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}