Home > Back-end >  Confusion with React Native Navigation
Confusion with React Native Navigation

Time:12-16

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>
     );
}
  • Related