React Native Navigate to Particular Screen When Notification is Clicked - reactjs

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

Related

React Native navigate between stacks - drawer to stack

I have a React Native app that I am adding navigation to. I have two different stacks I want to navigate between. I have searched SO and came up with the two versions which I have in my below code. The setup is that I have a main stack navigator which has a drawer stack and a normal stack (Auth) that I'd like to be able to go between. The main things are redirecting on login and logout from the header of the Drawer stack.
Below is my code, I am looking for help to figure out how to enable the mentioned navigation that I need the app to do. In theory, this is a common thing so I am pretty sure it should be supported and documented though I can't seem to find anything documented on it.
Error message
The action 'NAVIGATE' with payload {"name":"Auth","params":{}} was not handled by any navigator.
Do you have a screen named 'Auth'?
HeaderRight
export default function HeaderRight ({ navigation }) {
const { removeAuth } = useContext(AuthContext);
const logout = () => {
removeAuth()
// Note both are uncommented for sharing and are not actually meant to be when running the app
navigation.navigate('Auth', { screen: 'Login' }); // { screen: 'Login' }
navigation.navigate(
'Auth',
{},
NavigationActions.navigate({
routeName: 'Login'
})
)
}
return (
<View style={styles.header}>
<Icon name='logout' onPress={logout} size={28} style={styles.icon} color="white" />
</View>
)
}
Root navigation
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
drawer stack
export const DrawerStack = (props) => {
return (
<Drawer.Navigator
initialRouteName="Lists"
>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ListScreen}
/>
<Drawer.Screen
name="Room"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null,
}}
component={RoomScreen}
/>
</Drawer.Navigator>
);
};
auth stack
export const AuthStack = () => {
return (
<Stack.Navigator
initialRouteName={'Login'}
>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Forgot" component={ForgotScreen} />
</Stack.Navigator>
);
};
Edit
I think I have found the potential cause for this but I have no clue about the solution to it.
I have this in my root navigator (same as above)
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft />
),
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
This means that the stack/screen for Auth is only being rendered/exists when the drawer stack isn't shown. How can I refactor my root navigator to fix this?
I am thinking I need to somehow force the checkAuth function to re-render based on different actions taken or to watch a certain local storage.
This is the code for checkAuth
const checkAuth = () => {
try {
const authData = globalStorage.getString('accessToken')
if(authData !== null && authData !== undefined) {
setAuth(authData)
return true
} else {
return false
}
} catch(e) {
console.error(e)
return false
}
}
Context on what globalStorage is
import { MMKV } from 'react-native-mmkv'
export const globalStorage = new MMKV()

My component render in App and in Navigator are differents

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

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!

Delaying Navigation between screens with react navigation

What I want to achieve is when a user clicks on a screen to navigate to another screen, I want the transitioning between the screens to be delayed for 1 second or 2seconds.
I don't know if this is possible in
React navigation
.
I have this code that will transition from home to profile screen.
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const MyStack = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
And this is the home screen code
const HomeScreen = ({ navigation }) => {
return (
<Button
title="Go to Jane's profile"
onPress={() =>
navigation.navigate('Profile', { name: 'Jane' })
}
/>
);
};
And this is the profile screen code.
const ProfileScreen = ({ navigation, route }) => {
return <Text>This is {route.params.name}'s profile</Text>;
};
};
Is possible to move from home screen to profile screen with a delay in time
As #nithinpp suggested, You could achieve this by adding a setTimeout for 2s in the Home screen button onClick.
const HomeScreen = ({ navigation }) => {
return (
<Button
title="Go to Jane's profile"
onPress={() => {
setTimeout(()=> {
navigation.navigate('Profile', { name: 'Jane' });
}, 2000);
}}
/>
);
};
I don't really see a reason to use this code in any application, as this is not at all a good practice to have a hardcoded delay in your application code.

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