navigating user on notification click app in background/foreground - react-native

I'm using REACT-NATIVE-FCM.
My app has drawer navigator inside stack navigator in App.js.
On notification click from notification tray I want user to directly go to certain Screen.
Registering FCM events in Home page only work if app is closed, while in app in foreground the events are not called.It just takes you to the last open Screen.
I have to register FCM events on every Screen, which doesn’t look ok to me. So I have the solution to register FCM events in App.js since App.js is called from index.js while initializing the App but I’m unable to use this.props.navigation.navigate in App.js.
Using react navigation how can we redirect user just after stack is declared.
APP.JS
const Drawer = DrawerNavigator(
{
Dashboard: { screen: Dashboard },
Screen1: { screen: Screen1 },
Screen2: { screen: Screen2},
Screen3: { screen: Screen3},
},
{
navigationOptions: {
gesturesEnabled: false,
},
initialRouteName: "Dashboard",
contentOptions: {
activeTintColor: "#e91e63"
},
drawerPosition: 'right',
contentComponent: props => <SideBar {...props} />
}
);
const AppNavigator = StackNavigator(
{
Home: { screen: Home },
Drawer: { screen: Drawer },
ScreenABC: { screen: ScreenABC },
ScreenXYZ: { screen: ScreenXYZ }
},
{
headerMode: "none",
}
);
export default () =>
<Root>
<AppNavigator />
</Root>;
//CALLED WHEN APP IN FOREGROUND
this.notificationListener = FCM.on(FCMEvent.Notification, async (notif) => {
//alert('FCM.on: ' + JSON.stringify(notif));
if(notif.routeValue == 1)
{
this.props.navigation.navigate("Screen1")}
});
//CALLED WHEN APP IN BACKGROUND
FCM.getInitialNotification().then(notif => {
});

If you want to really go to a certain screen no matter what. You should at least reset your navigator state before you call navigate.
Or you can also do is creating a custom action, dispatch this action and return a correct navigator state.
Here is how I reset my navigator state to home:
export const homeNav = (state, action) => {
let index;
let routes;
switch (action.type) {
case types.RESET_HOME_NAV:
routes = [...state.routes];
for (let index = state.index; index > 0; --index) {
routes.pop();
}
return {
...state,
routes,
index: 0,
};
default:
return NavigatorHome.router.getStateForAction(action, state);
}};

Related

Prevent for user to access "Auth Loading Screen" on android

In my app (and most likely like any other app) I have a loading screen which checks if the user is either logged in or not. Thing is that the user on android right now has the possibility of returning to that screen if the "back" button is pressed. How is it possible for me to avoid for the user doing that?
This is what I have on my code:
const loadingStack = createSwitchNavigator({
Loading: {
screen: Loading
}
}, {
defaultNavigationOptions: {
unmountOnBlur: true,
headerMode: 'none',
gestureEnabled: false
}
}
);
export default createAppContainer(
createStackNavigator({
Loading: loadingStack,
App: {
screen: AppTabNavigator,
navigationOptions: {
gestureEnabled: false,
},
},
Auth: {
screen: AuthStack,
},
...
I see two solutions:
Change your navigation and opt for a switch navigation instead.
createAppContainer(
createSwitchNavigator(
{
Loading: {
screen: Loading,
navigationOptions: {
headerMode: 'none',
}
},
App: {
screen: AppTabNavigator,
navigationOptions: {
gestureEnabled: false
}
},
Auth: {
screen: AuthStack
}
}
)
)
If you want to "enjoy" the animations of the StackNavigator, when you navigate from your Loading to your App, you replace your current view with your new view.
//Example on your Loading Screen
this.props.navigation.replace({ routeName: "App" })
This will replace your Loading Screen view with your App "View", when you go back, you will not be redirected to the Loading Screen anymore because it is no longer in the stack, it has been replaced by App.
EDIT (In response to the comment because it's too long):
In this case, if you want to make a special backButton management on the Home you can use the React Native BackHandler.
For example, if you want nothing to happen when you click on the backButton, add an eventListener that returns true no matter what:
import { BackHandler } from 'react-native'
...
componentDidMount() {
this.backHandler = BackHandler.addEventListener('hardwareBackPress', () => true)
}
componentWillUnmount(){
this.backHandler.remove()
}
Otherwise, if you want the app to quit, for example:
this.backHandler = BackHandler.addEventListener('hardwareBackPress', () => BackHandler.exitApp())

Disable loading of initialRoute inside stack navigator when

I have two stacks in my react-native app similar to this structure. When I navigate a user from Home screen inside MainStack to Quiz screen inside QuizStack, the componentDidMount method of QuizList screen is being called. How do I prevent this? I want only the componentDidMount of Quiz screen to be called.
export const MainStack = createStackNavigator({
Home: { screen: Home },
Game: { screen: Game },
Leaderboard: { screen: Leaderboard }
}, { initialRouteName: 'Home' })
export const QuizStack = createStackNavigator({
QuizList: { screen: QuizList },
Quiz: { screen: Quiz },
QuizResult: { screen: QuizResult }
}, { initialRouteName: 'QuizList' })
export const InternalStacks = createBottomTabNavigator({
QuizStack: { screen: QuizStack, navigationOptions: { tabBarLabel: 'Quiz' } },
MainStack: { screen: MainStack, navigationOptions: { tabBarLabel: 'Home' } },
})
What makers matters worse is, on sending a push notification, users are directed automatically from Home(MainStack) to Quiz (QuizStack). The componentDidMount method in Quiz calls an API, so if 10K users opens notifications, that 10K APIs called. Then unecesarily the componentDidMount method of QuizList(QuizStack) is also being called, which is an additional 10K APIs
It is unclear to me why you have two separate stacks, and how they are connected. Maybe by a top level navigator? You should try to merge the stacks into a single one.
About the Push notification, you can create a SwitchNavigator:
const SwitchNavigator = createSwitchNavigator(
{
Loading: LoadingScreen,
Root: RootStack
},
{
initialRouteName: 'Loading'
}
)
And in the componentDidMount in LoadingScreen, you will check the initial URL received by the push.
async componentDidMount() {
const { navigation } = this.props
const initialURL = await Linking.getInitialURL()
if (initialURL && initialURL.startsWith('myapp://myapp/xxx')) {
navigation.navigate('MyScreenA', { options })
} else {
navigation.navigate('MyScreenDefaultScreen')
}
}

React Navigation v3 Modal does not work with createBottomTabNavigator

React Navigation v3 Modal does not work with createBottomTabNavigator and not sure why. However, headerMode: 'none' seems to be working but mode: 'modal' is not showing up as modal.
const Addpicture = createStackNavigator(
{
Addpicture: {
screen: Addpicture
}
},
{
mode: 'modal', // This does NOT work
headerMode: 'none' // But this works
}
);
const Navigator = createBottomTabNavigator(
{
'My Interviews': {
screen: MyDatesStack
},
'Add Interview': {
screen: AddDateStack
},
'Companies': {
screen: CompaniesStack
}
}
);
export default createAppContainer(Navigator);
Indeed it doesn't work no matter what I tried.
I solved this problem by following steps below. Let's say you want to open modal when NewModal tab is pressed.
Set up your app container by including tabs stack & to be opened modal navigation stack:
const FinalTabsStack = createStackNavigator(
{
Home: TabNavigator,
NewModal: NewModalNavigator
}, {
mode: 'modal',
}
)
Create app container with that tabs stack per this guide
Inside the TabNavigator in the createBottomTabNavigator return null component for specific tab (NewModal) (to turn off navigation by react-navigator)
const TabNavigator = createBottomTabNavigator({
Feed: FeedNavigator,
Search: SearchNavigator,
NewModal: () => null,
Chat: ChatNavigator,
MyAccount: MyAccountNavigator,
}
defaultNavigationOptions: ({ navigation }) => ({
mode: 'modal',
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
if (routeName === 'NewModal') {
return <NewModalTab isFocused={focused} />;
}
},
}),
Handle click manually inside a custom tab component NewModalTab with TouchableWithoutFeedback & onPress. Inside NewModalTab component:
<TouchableWithoutFeedback onPress={this.onPress}>
<Your custom tab component here />
</TouchableWithoutFeedback>
Once you catch onPress dispatch redux event
onPress = () => {
this.props.dispatch({ type: 'NAVIGATION_NAVIGATE', payload: {
key: 'NewModalNavigator',
routeName: 'NewSelfieNavigator',
}})
}
Handle dispatched event using Navigation Service. I'm using redux-saga for it
NavigationService.navigate(action.payload);
A bit complicated but works.

Remove screen from stack navigator

I’ve observed Back button logic works seeing the sequence of screens in the stack. I’ve a Drawer navigator placed inside stack navigator.
On top of the stack I’ve Splash screen. On Dashboard when I press back button it takes me to splash screen.
How can I remove splash screen from stack after entering in the app, So when I press back button Dashboard it will EXIT the app instead of taking to SPLASH SCREEN.
/* #flow */
import React from "react";
import { Platform, Text } from "react-native";
import { Root } from "native-base";
import { StackNavigator, DrawerNavigator} from "react-navigation";
import Register from "./components/Register";
import Available from "./components/Available";
import Splash from "./components/splash/“;
import SideBar from "./components/sidebar";
import Discover from "./components/Discover/";
import Dashboard from "./components/Dashboard/";
import Contact from "./components/Contact"
const Drawer = DrawerNavigator(
{
Dashboard: { screen: Dashboard },
Discover: { screen: Discover },
Contact: { screen: Contact},
},
{
navigationOptions: {
gesturesEnabled: false,
},
initialRouteName: "Dashboard",
contentOptions: {
activeTintColor: "#e91e63"
},
drawerPosition: 'right',
contentComponent: props => <SideBar {...props} />
}
);
const AppNavigator = StackNavigator(
{
Splash: { screen: Splash },
Drawer: { screen: Drawer },
Available: { screen: Available },
Register: { screen: Register },
},
{
// initialRouteName: “Splash”,
headerMode: "none",
}
);
export default () =>
<Root>
<AppNavigator />
</Root>;
One solution would be to reset the stack inside the splash screen component and redirect the user to the screen that you prefer:
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Drawer'})
]
})
this.props.navigation.dispatch(resetAction)
For newer versions of react-navigation :
import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Profile' })],
});
this.props.navigation.dispatch(resetAction);
Link to the official documentation
One of the simplest solutions would be, replace current screen with new screen so user won't be allowed to go back.
const SplashScreen: () => {
...
navigation.replace("LoginScreen"); // Move forward and Remove this screen from stack
see https://reactnavigation.org/docs/stack-actions/#replace
I solved it using this
code:
const prevGetStateForActionHomeStack = HomeStack.router.getStateForAction;
HomeStack.router = {
...HomeStack.router,
getStateForAction(action, state) {
if (state && action.type === 'ReplaceCurrentScreen') {
const routes = state.routes.slice(0, state.routes.length - 1);
routes.push(action);
return {
...state,
routes,
index: routes.length - 1,
};
}
return prevGetStateForActionHomeStack(action, state);
},
};
replaceScreen = () => {
const { locations, position } = this.props.navigation.state.params;
this.props.navigation.dispatch({
key: 'NearMeMap',
type: 'ReplaceCurrentScreen',
routeName: 'NearMeMap',
params: { locations, position },
});
};
Also if you need in-depth explanation of the code, watch this short video here
In react-navigation 5.x version. If you want to open new screen and remove previous stack screen, you can write: -
navigation.dispatch(StackActions.replace('screen_name', {params: {}}));
The replace action allows to replace a route in the navigation state. You can import it as:-
import { StackActions } from '#react-navigation/native';
If you want to perform other actions on stack navigation check out this link-
https://reactnavigation.org/docs/stack-actions
useEffect(() => {
setTimeout(() => {
navigation.reset({
index:0,
routes:[
{
name:"Welcome",
// params:{key:'param'},
},
],
});
}, 1000);},[]);
You can also trigger the this onPress like:
onPress={()=>navigation.reset({
index:0,
routes:[
{
name:"NameOfScreen",
// params:{key:'param'},
},
],
})}

How to handle click event on tab item of TabNavigator in React Native App using react-navigation?

I am programming React Native App. I am using react-navigation for navigating screens.
I have 2 Stack Navigators, they are stayed in a Tab Navigator. I want handle click event when I press tab item on TabBar to refresh its view.
export const HomeStack = StackNavigator({
Home: {screen: ScreenHome},
Detail: {screen: ScreenDetail}
})
export const UserStack = StackNavigator({
User: {screen: ScreenUser}
})
export const Tabbar = TabNavigator({
HomeTab: {
screen: HomeStack,
},
UserTab: {
screen: UserStack,
}
}, {
tabBarComponent: ({ jumpToIndex, ...props}) => (
<TabBarBottom
{...props}
jumpToIndex={(index) => {
if(props.navigation.state.index === index) {
props.navigation.clickButton(); //----> pass props params (code processes)
}
else {
jumpToIndex(index);
}
}
}
/>
),
tabBarPosition: 'bottom'
});
class TabClass extends Component{
constructor(props){
super(props)
this.clickButton = this.clickButton.bind(this)
}
clickButton(){
console.log('click button')
}
render (){
return (
<Tabbar clickButton={()=> this.clickButton()}/>
)
}
}
I want to pass clickButton() function from TabClass Component to the code which processes event click tab bar. When if(props.navigation.state.index === index), I want it will call clickButton(). I try it but it doesn't work.
Is there any way to solve my matter?
I tried onNavigationStateChange(prevState, currentState).
class TabClass extends Component{
constructor(props){
super(props)
this.clickButton = this.clickButton.bind(this)
}
clickButton(){
console.log('click button')
}
_handleNavagationStateChange(prevState, currentState){
console.log('handle navigation state change')
}
render (){
return (
<Tabbar onNavigationStateChange={(prevState, currentState) => {
this._handleNavagationStateChange(prevState, currentState)
}}
clickButton={()=> this.clickButton()}
/>
)
}
}
However, _handleNavagationStateChange(prevState, currentState) only run when navigation state changes (for examples, if I stay at HomeTab, I click User Tab Item, this function will run; if I stay at HomeTab, I click Home Tab Item, this function will not run).
Is there any way to handle click event on tab item.
according to the newest documentation here, you can use navigationOptions: {navigationOptions: () => doWhatever()} to handle tab bar taps.
From the react-navigation 4.x docs, you can override tabBarOnPress within navigationOptions:
tabBarOnPress: ({ navigation, defaultHandler }) => {
defaultHandler(); // Call the default handler to actually switch tabs
// do extra stuff here
},
Please try the code following when customize the event touch of TabBar:
import { TabBarBottom, TabNavigator, StackNavigator } from 'react-navigation';
export const MainScreenNavigator = TabNavigator({
Home: { screen: HomeViewContainer },
Search: { screen: SearchViewContainer },
}, {
tabBarComponent: ({ jumpToIndex, ...props, navigation }) => (
<TabBarBottom
{...props}
jumpToIndex = { tabIndex => {
const currentIndex = navigation.state.index;
if (tabIndex == 1) {
// do some thing. Call Native Live Stream Record
} else {
jumpToIndex(tabIndex);
}
console.log('Select tab: ', tabIndex);
}}
/>),
tabBarPosition: 'bottom',
});