My component render in App and in Navigator are differents - reactjs

I am developing an app in React Native on Expo during few weeks now.
In my App.tsx i am doing this :
const App = () => {
const [producteurs, setSearchProducteurs] = useState('');
useEffect(() => {
sql.initDB();
sql.insertInDB();
sql.search().then((data: any)=>{
setSearchProducteurs(data)
}).catch((error) => console.log(error));
})
const isLoadingComplete = useCachedResources();
const colorScheme = useColorScheme();
const Stack = createNativeStackNavigator<RootStackParamList>();
if (!isLoadingComplete) {
return null;
} else {
return (
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Root"
component={Map}
options={{ title: 'My home' }}
/>
</Stack.Navigator>
</NavigationContainer>
<Map company_name={producteurs} />
<StatusBar />
</SafeAreaProvider>
);
}
}
But my Map component rendering twice on my screen. I have a header with "My home" and an empty map below, and below this map, i have a second map but the right one, with data fetch in my App.tsx.
Do you know why this behaviour is like that ?
If i remove all my NavigationContainer, i have only my component which is good, with data. But i have no more my Navigation..
Thanks in advance for help

Related

React Native Expo - show different NavigationContainer depending on whether user is logged in or not

App.js:
const Stack = createNativeStackNavigator();
const Drawer = createDrawerNavigator();
export default function App() {
let isLoggedIn = false;
setInterval(function () {
auth.onAuthStateChanged((user) => {
isLoggedIn = user !== null;
});
}, 50);
if (!isLoggedIn) {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="LoginScreen" component={LoginScreen} />
<Stack.Screen name="StartScreen" component={StartScreen} />
</Stack.Navigator>
</NavigationContainer>
);
} else if (isLoggedIn) {
return (
<>
<NavigationContainer>
<Drawer.Navigator initialRouteName="StartScreen">
<Drawer.Screen name="StartScreen" component={StartScreen} />
<Drawer.Screen name="LoginScreen" component={LoginScreen} />
</Drawer.Navigator>
</NavigationContainer>
</>
);
}
}
const styles = StyleSheet.create({});
AppRegistry.registerComponent("myApp", () => App);
My goal is to only display the Drawer.Navigator when the user is logged in.
I tried updating a boolean isLoggedIn to change the return statement but it is no working, altough the boolean is changing values.
Any ideas on how to approach this?
Thanks in advance
Refactor code as below:
//...Other top-level imports
import { ActivityIndictor } from "react-native";
const Stack = createNativeStackNavigator();
const Drawer = createDrawerNavigator();
export default function App() {
const [isLoggedIn, setLoggedIn] = React.useState(null);
const authenticateUser = () => {
auth.onAuthStateChanged((user) => {
if (user) {
setLoggedIn(true);
} else {
setLoggenIn(false);
}
});
};
// Check authentication state when the app launched
React.useEffect(() => {
if (!isLoggedIn) {
authenticateUser();
}
}, [isLoggedIn]);
/** While user is authenticating, show progress indicator*/
if (isLoggedIn === null) return <ActivityIndicator />;
return (
<NavigationContainer>
{isLoggedIn ? (
<Drawer.Navigator initialRouteName="StartScreen">
<Drawer.Screen name="StartScreen" component={StartScreen} />
<Drawer.Screen name="LoginScreen" component={LoginScreen} />
</Drawer.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen name="LoginScreen" component={LoginScreen} />
<Stack.Screen name="StartScreen" component={StartScreen} />
</Stack.Navigator>
)}
</NavigationContainer>
);
}
const styles = StyleSheet.create({});
AppRegistry.registerComponent("myApp", () => App);
Click on the link https://reactnavigation.org/docs/auth-flow to read about how auth flows work in react native.

Passing state to parent and then to child

I have app.js which assigns values to a state. I am then trying to pass that state to a Home screen which renders a Screen1. I need this state in Screen1.
You can see I am trying to display the users latitude and longitude on Screen1 but I am not able to render anything.
I have listed the code below as well as in this snack demo here. The goal is for the state to go from App.js -> Home.js -> Screen1.js
EDIT:: I might use React native context but never have before. If the current way I am doing it is considered a poor practice then context is the way to go.
Home.js
export default function Home({ navigation, latitude, longitude }) {
return (
<View style={styles.container}>
<Screen1 />
</View>
);
}
Screen1.js
export default class Screen1 extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.paragraph}>latitude:</Text>
<Text style={styles.paragraph}>{this.props.latitude}</Text>
<Text style={styles.paragraph}>longitude:</Text>
<Text style={styles.paragraph}>{this.props.longitude}</Text>
</View>
);
}
}
export default function MyTabs() {
const [latitude, setLatitude] = useState(null);
const [longitude, setLongitude] = useState(null);
const permissionAlert = () => {
Alert.alert(
'You did not allow location permissions',
'Please go to settings and allow location permissions for full functionality',
[
],
{
cancelable: true
}
);
}
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
permissionAlert();
return;
}
let location = await Location.getCurrentPositionAsync({});
setLatitude(location.coords.latitude)
setLongitude(location.coords.longitude);
})();
}, []);
console.warn("latitude: ", latitude);
console.warn("longitude: ", longitude);
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home">
<Stack.Screen name="Home" options={{headerShown: false}}>
{(props) => <Home {...props} latitude={latitude} longitude={longitude} />}
</Stack.Screen>
<Stack.Screen name="Screen1" options={{headerShown: false}}>
{(props) => <Screen1 {...props} latitude={latitude} longitude={longitude} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
You need to pass latitude/longitude from MyTabs -> Home -> Screen1. Currently, you're setting them on Home, but not on Screen1. To fix, add the props to <Screen1 /> in Home.js
export default function Home({ navigation, latitude, longitude }) {
return (
<View style={styles.container}>
<Screen1 latitude={latitude} longitude={longitude} />
</View>
);
}
It's also worth noting that you're rendering Screen1 both
directly within MyTabs
indirectly in MyTabs, when you render Home
Basically, make sure you always pass all expected props when you render a component and you should be good to go!

React Native Navigate to Particular Screen When Notification is Clicked

I am trying to implement navigation upon the user click on a notification that they have received. I am successfully receiving the notifications with expo-notifications and accept data (routes) from API but unable to navigate to another screen when user clicks on a notification.
useNotifications:
export default useNotifications = () => {
...
useEffect(() => {
registerForPushNotificationsAsync().then((token) => {
setExpoPushToken(token);
alert(token);
});
notificationListener.current = Notifications.addNotificationReceivedListener(
(notification) => {
setNotification(notification);
console.log(notification);
}
);
responseListener.current = Notifications.addNotificationResponseReceivedListener(
(response) => {
//notification is received OK
console.log("opened");
//here I want to navigate to another screen using rootnavigation
navigation.navigate("Account");
//alert shows fine
alert("ok");
}
);
return () => {
Notifications.removeNotificationSubscription(notificationListener);
Notifications.removeNotificationSubscription(responseListener);
};
}, []);
};
navigator:
const SettingsNavigation = ({ component }) => {
useNotifications();
return (
<Stack.Navigator mode="card" screenOptions={{ headerShown: false }}>
<Stack.Screen
name="Main"
component={component}
options={{ title: "Home" }}
/>
<Stack.Screen
name="Timetable"
component={TimetableScreenBoss}
options={menuOptions("Schedule")}
/>
<Stack.Screen
name="Account"
component={AccountNavigator}
options={menuOptions("Account", false)}
/>
</Stack.Navigator>
);
};
root navigation:
import React from "react";
export const navigationRef = React.createRef();
const navigate = (name, params) =>
navigationRef.current?.navigate(name, params);
export default {
navigate,
};
app.js:
import { navigationRef } from "./app/navigation/rootNavigation"; //rootnavigation
<NavigationContainer navigationRef={navigationRef}>
<CustomNavigator>
</NavigationContainer>
Assuming you are using react-navigation, the NavigationContainer accepts a normal ref prop:
<NavigationContainer ref={navigationRef}>
<CustomNavigator>
</NavigationContainer>
see NavigationContainer docs

Im trying to add nested navigation to my react native project

Im following a react native app tutorial that has stack navigation only but I'm trying to also add nested navigation so I have a tab bar at the bottom that leads to additional pages I'm building but can't seem to figure out how to implement in my current code. Here is my code so far not sure if i'm on the right track or way off
{
DashBoard: DashBoardScreen,
Articles: ArticlesScreen,
Article: ArticleScreen,
Search: SearchScreen
},
{
initialRouteName: 'DashBoard',
defaultNavigationOptions: ({navigation}) => ({
headerStyle, headerTitleStyle,
headerRight: (<SearchBtn onPress={() => navigation.navigate('Search')}/>)
})
}
);
const Tab = createBottomTabNavigator();
const TabNavigator = () => (
<Tab.Navigator>
<Tab.Screen name="News" component={DashBoard}/>
<Tab.Screen name="Game" component={Game}/>
</Tab.Navigator>
)```
Here's how you can achieve this:
const BottomTab = createBottomTabNavigator();
const Stack = createStackNavigator();
const AppStack = () => {
return (
<BottomTab.Navigator>
<BottomTab.Screen name="Screen1" component={Screen1} />
<BottomTab.Screen name="Screen2" component={Screen2} />
</BottomTab.Navigator>
);
};
const MainStack = () => {
return (
<Stack.Navigator headerMode="none">
<Stack.Screen name="App" component={AppStack} />
<Stack.Screen name="Auth" component={AuthStack} />
</Stack.Navigator>
);
};

Override Header Title React Navigation V5

I have created a nested tab navigator in my stack navigator as follows:
const Stack = createStackNavigator()
const Tab = createBottomTabNavigator();
function TabNav () {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen}></Tab.Screen>
<Tab.Screen name="Category" component={CategoryScreen}></Tab.Screen>
<Tab.Screen name="Settings" component={SettingsScreen}></Tab.Screen>
</Tab.Navigator>
)
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
<Stack.Screen name="Category" component={CategoryScreen}>
</Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen" component= {SpecialBeerScreen}>
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)
}
However, the header now looks like Tab for each of the screens. How do I override this Header for each of the screens with a custom text, such as Home, Category etc.
Update: This is still valid in react-navigation v6
Use getFocusedRouteNameFromRoute as outlined in the react-navigation docs in the section Setting parent screen options based on child navigator's state to access information of screens in nested navigators.
Change this line
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
to the following (adding the options prop):
<Stack.Screen
name="Tab"
component={TabNav}
options={({ route }) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
switch (routeName) {
case 'Category': {
return {
headerTitle: 'Category',
};
}
case 'Settings': {
return {
headerTitle: 'Settings',
};
}
case 'Home':
default: {
return {
headerTitle: 'Home',
};
}
}
}}
/>
You also have to import getFocusedRouteNameFromRoute, so also add:
import { getFocusedRouteNameFromRoute } from '#react-navigation/native';
If I understand your problem correctly, you wanted to change the stack title when the tab is changed. In this case, you may use React Context to control it.
(I also put this code in snack https://snack.expo.io/#gie3d/change-header-from-tab)
edit: 1, I separated it into 3 files and assume that it's all in the same directory.
HomeTitleContext.js
export const HomeTitleContext = React.createContext({
title: 'default title',
setTitle: () => {},
});
App.js
import { HomeTitleContext } from './HomeTitleContext';
export default function App() {
const [title, setTitle] = useState('default title');
return (
<HomeTitleContext.Provider
value={{
title,
setTitle,
}}
>
<HomeTitleContext.Consumer>
{(ctx) => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Tab"
component={TabNav}
options={{ title: ctx.title }} // The title will be dynamically changed here
/>
<Stack.Screen
name="Category"
component={OtherScreen}
></Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen"
component={OtherScreen}
></Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)}
</HomeTitleContext.Consumer>
</HomeTitleContext.Provider>
);
}
In your component, for example: HomeScreen, you set up a useFocusEffect and change the title from setTitle which you'll get from the context
HomeScreen.js
import React, { useContext } from 'react';
import { useFocusEffect } from '#react-navigation/native';
import { View, Text } from 'react-native';
import { HomeTitleContext } from './HomeTitleContext';
const HomeScreen = ({ navigation }) => {
const { setTitle } = useContext(HomeTitleContext);
useFocusEffect(() => {
setTitle('this is home');
});
return (
<View>
<Text>Home</Text>
</View>
);
};

Resources