I can't figure out how to navigate from push notifications from firebase fcm. I was having issues with navigation being undefined so I followed this guide, but it still doesn't change screens. Is there some basic step I am missing. I have tried to navigate to several other screens without params and none navigate, and I am not getting any errors:
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
Here is my navigation service:
this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen) => {
const { data } = notificationOpen.notification;
NavigationService.navigate("Chat", {
chatName: `${data.channelName}`,
chatId: `${data.channelId}`
});
});
And here is my main App file:
import SplashScreen from 'react-native-splash-screen';
const TopLevelNavigator = createStackNavigator({
ChatApp
},
{
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
});
const AppContainer = createAppContainer(TopLevelNavigator);
class App extends Component {
async componentDidMount() {
SplashScreen.hide();
}
render() {
return (
<Provider store={store}>
<AppContainer ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}} />
</Provider>)
}
}
export default App;
The issue was that I needed to have the other stack inside of the same navigator. Once I added that it started working
const TopLevelNavigator = createStackNavigator({
ChatApp: {
screen: ChatApp,
},
Chat: {
screen: ChatScreen,
}
});
Related
code
const MypageStack = createStackNavigator(
{
News,
Mypage,
},
{
initialRouteName: 'Mypage',
},
);
const postLoginNavigator = createBottomTabNavigator({
Mypage: MypageStack,
});
const AppNavigator = createStackNavigator({
Loading,
First,
PostLogin: postLoginNavigator,
}, {
mode: 'card',
headerMode: 'none',
initialRouteName: 'Loading',
defaultNavigationOptions: {
gestureEnabled: false,
},
});
const AppContainer = createAppContainer(AppNavigator);
export default class App extends React.Component {
async componentDidMount() {
Notifications.addListener(this.subscribeNotification);
}
subscribeNotification = (notification) => {
const { screen = null } = data;
// screen = 'News'
if (notification.origin === 'selected') {
if (screen) {
dispatch(NavigationActions.navigate({ routeName: screen }));
}
}
}
render() {
return (
<AppContainer />
);
}
}
What I'm trying to do
When opening notification, I want to navigate to News screen.
But, dispatch(NavigationActions.navigate({ routeName: screen })); doesn't work.
I got an error.
can't find variable: dispatch
I don't know how to navigate. I would appreciate it if you could give me any advice.
To dispatch a navigation action you have to use a root navigation object from you props like this =>
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'HOME_SCREEN' })] });
this.props.navigation.dispatch(resetAction);
But I found it more convenient to create a common file called NavigationService.js to manage your navigation actions, like this one
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
function getCurrentRoute() {
let route = _navigator.state.nav
while (route.routes) {
route = route.routes[route.index]
}
return route
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator,
getCurrentRoute
};
Update your <AppContainer/> to pass the navigation refs to NavigationSerive file
<AppContainer ref={ref => NavigationService.setTopLevelNavigator(ref) }
And then anywhere in your app just import and call whatever you need
import NavigationService from './NavigationService';
NavigationService.navigate(routeName, { ...yourParams })
Happy Coding
I am using HOC to get current user details on login for each route in my react native application using ApolloClient.
Below is my main component App.tsx:
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { createStackNavigator } from 'react-navigation-stack';
const client = new ApolloClient({
cache,
link: errorLink.concat(authLink.concat(httpLink))
});
const TabStack = createBottomTabNavigator({
Feed: {
screen: Feed
},
Upload: {
screen: Upload
},
Profile: {
screen: Profile
}
})
const MainStack = createStackNavigator(
{
Home: { screen: TabStack },
User: { screen: UserProfile },
Comments: { screen: Comments },
Post: { screen: Post }
},
{
initialRouteName: 'Home',
mode: 'modal',
headerMode: 'none'
}
)
const AppContainer = createAppContainer(MainStack);
//Here I assign HOC to AppContainer
const RootWithSession = withSession(AppContainer);
class App extends PureComponent {
constructor(props) {
super(props);
}
render() {
return (<ApolloProvider client={client}>
<RootWithSession/>
</ApolloProvider>)
}
}
export default App;
Below is HOC withSession.tsx:
export const withSession = Component=>props=>(
<Query query={GET_CURRENT_USER}>
{({data,loading,refetch})=>{
if (loading) {
return null;
}
console.log(data);
return <Component {...props} refetch={refetch}/>;
}}
</Query>
)
I want to pass refetch function to all the components
<Component {...props} refetch={refetch}/>
How to do this? Any help is greatly appreciated.
I was able to pass refetch function to all Components from HOC withSession component using ScreenProps of StackNavigator as below:
export const withSession = Component=>props=>(
<Query query={GET_CURRENT_USER}>
{({data,loading,refetch})=>{
if (loading) {
return null;
}
if(props && props.navigation){
//props.refetch = refetch;
console.log(props)
//props.navigation.refetch = refetch;
props.navigation.setParams({refetch});
}
return <Component screenProps={{refetch}} {...props} refetch={refetch}/>;
}}
</Query>
)
Now, I am able to get refetch method using this.props.screenProps.refetch
I have 2 screens:
Home Screen
Login Screen
My entry point is my Home screen, if I am logged in (fetching datas), log out and then log in to another account. Since my Home component is already mounted, my data will not be updated and this.getDatas() will not be called again. I can't find solutions, I think I need to use "ComponentWillUpdate" in my Home.js to call this.getDatas() or something like that but you can't find the solution.
Here's my code:
Home.js
componentDidMount = async () => {
const { navigation } = this.props;
if (!await AsyncStorage.getItem('auth')) {
navigation.navigate('Login');
} else {
this.getDatas();
}
}
getDatas() {
axios.get(`${apiUrl}/me`).then((res) => {
this.setState({
datas: res.data.datas,
});
}).catch((err) => {
console.warn(err);
});
}
Login.js
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null,
};
}
logIn() {
const { email, password } = this.state;
axios.post(`${apiUrl}/auth`, {
email,
password
}).then((res) => {
navigation.navigate('Home');
}).catch((err) => {
console.warn('Error:', err);
});
}
}
App.js
const MainNavigator = createStackNavigator({
Home,
CreateGame,
GamePreview,
CardSelection,
BonusCard,
Game,
GameResult
}, { initialRouteName: 'Home', headerMode: 'none', });
const LoginNavigator = createStackNavigator({
Login,
Subscribe,
SubscribeNext,
ForgottenPassword,
}, { initialRouteName: 'Login', headerMode: 'none', });
const RootNavigator = createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
LoginNavigator,
MainNavigator
}, { initialRouteName: 'AuthLoading' });
export default class App extends Component {
render() {
return <RootNavigator />;
}
}
Any idea ?
You should consider using Switch Navigator from react-navigation for your authentication flows in your application. So once you get logged out of your app and lands on login, the application screen pops out of the memory and thus when you login back, the componentDidmount will again be triggered.
I'm using react-navigation library. Currently the navigation is organized in this way:
App.js:
const Layout = createRootNavigator(signedIn);
return (
<AppFontLoader>
<Layout />
</AppFontLoader>
);
AppNavigator:
export const createRootNavigator = (signedIn = false) => {
return createSwitchNavigator(
{
SignedIn: {
screen: SignedIn
},
SignedOut: {
screen: SignedOut
}
},
{
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
);
};
AppNavigator:
export const SignedIn = createMaterialBottomTabNavigator(
{
MeetingsScreen: {
...
}
MeetingsScreen:
const MeetingNavigator = createStackNavigator({
MeetingsListScreen: {
screen: MeetingsListScreen,
navigationOptions: {
}
},
AddMeetingForm: {
screen: AddMeetingFormScreen
},
MeetingScreen: {
screen: MeetingScreen
}
}, {initialRouteName: "MeetingsListScreen"});
The error is shown with the current structure:
You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator.
Apparently, I shouldn't nest one navigator into another, but I'm struggling to come up with the right navigation structure.
How to organize the navigation so that I can have more layers of navigation to move between screens?
I've encountered the same issue, so what I ended up doing was just making one navigation. Rootstack. All routes are there. My app.js has only root stack and navigations.
I am using React Native, react-native-onesignal, and react-navigation. When my action button in the push notification is pressed, I am trying to navigate to a screen or just show a modal pop up that I can render another screen in.
I cannot get this to work. I keep getting errors like:
"Cannot read property 'navigation' of undefined" or "this.setState is not a function"
I have tried to navigate to another screen and I have tried to set a visible state to true of a modal popup and failed to succeed at both. So, I think I may be misunderstanding something all together with my approach.
Here is some code to help everyone further understand the problem:
note: I am aware that I don't have a 'navigation' prop reference, but I am not sure how to access it. I thought is was available to me from the child element in my router.js file.
additional note: I think the problem is because I am trying to navigate somewhere or render a popup from the 'onOpened(openResult)' handler function. Not sure how to include this in the actual render part of the component if that is what is needed.
THIS IS THE APP CONTAINER (index.js):
import React, { Component } from 'react';
import { Text, Modal, View, Alert } from 'react-native';
import OneSignal from 'react-native-onesignal';
import { Root } from './config/router';
class App extends Component {
constructor(props) {
super(props);
this.state = { modalVisible: false };
this.setModalVisible = this.setModalVisible.bind(this);
}
componentWillMount() {
OneSignal.addEventListener('received', this.onReceived);
OneSignal.addEventListener('opened', this.onOpened);
OneSignal.addEventListener('registered', this.onRegistered);
OneSignal.addEventListener('ids', this.onIds);
}
componentDidMount() {
FingerprintScanner
.isSensorAvailable()
.catch(error => this.setState({ errorMessage: error.message }));
}
componentWillUnmount() {
OneSignal.removeEventListener('received', this.onReceived);
OneSignal.removeEventListener('opened', this.onOpened);
OneSignal.removeEventListener('registered', this.onRegistered);
OneSignal.removeEventListener('ids', this.onIds);
}
onReceived(notification) {
console.log('Notification received: ', notification);
}
onOpened(openResult) {
console.log('Message: ', openResult.notification.payload.body);
console.log('Data: ', openResult.notification.payload.additionalData);
console.log('isActive: ', openResult.notification.isAppInFocus);
console.log('openResult: ', openResult);
if (openResult.action.actionID === 'fingerprint_id') {
console.log('fingerprint_id: ', 'clicked');
//this.props.navigation.navigate('tbdScreen');
//or
//this.setModalVisible(true)
}
}
onRegistered(notifData) {
console.log('Device had been registered for push notifications!', notifData);
}
onIds(device) {
console.log('userId: ', device.userId);
}
setModalVisible(visible) {
this.setState({ modalVisible: visible });
}
render() {
return (
<Root>
<View>
<Modal
animationType={"slide"}
transparent={false}
visible={this.state.modalVisible}
>
<View>
<Text>this is the modal!</Text>
</View>
</Modal>
</View>
</Root>
);
}
}
export default App;
THIS IS MY ROUTER FILE (router.js):
import React from 'react';
import { TabNavigator, StackNavigator } from 'react-navigation';
import WelcomeScreen from '../screens/WelcomeScreen';
import AuthScreen from '../screens/AuthScreen';
import HomeScreen from '../screens/HomeScreen';
import SettingsScreen from '../screens/SettingsScreen';
import TBDScreen from '../screens/TBDScreen';
export const HomeStack = StackNavigator({
home: {
screen: HomeScreen
},
settings: {
screen: SettingsScreen,
navigationOptions: {
title: 'Settings'
}
}
}
);
export const Tabs = TabNavigator({
welcome: {
screen: WelcomeScreen,
navigationOptions: {
TabBarLabel: 'Welcome'
}
},
auth: {
screen: AuthScreen,
navigationOptions: {
TabBarLabel: 'Auth'
}
},
home: {
screen: HomeStack,
navigationOptions: {
TabBarLabel: 'HomeScreen'
}
}
},
{
tabBarPosition: 'bottom'
});
export const Root = StackNavigator({
Tabs: {
screen: Tabs,
},
tbdScreen: {
screen: TBDScreen,
}
},
{
mode: 'modal',
headerMode: 'none',
});
I have also been looking into using Redux with my React Navigation to persist the navigation state. Maybe this is what I need to do to access the navigation actions of the router.js file from the App component in the index.js file?
Anyway, much thanks in advance as I have been stuck on this for a while and I'm looking to learn from this problem.
Your onOpened function does not have the right scope because it is being called within OneSignal's addEventListener function so you have to bind "this" as below.
Change
OneSignal.addEventListener('opened', this.onOpened);
to
OneSignal.addEventListener('opened', this.onOpened.bind(this));
And you will have this.props.navigation.navigate available inside the onOpened function.
Just do as follows:
componentWillMount() {
this.onReceived = this.onReceived.bind(this);
this.onOpened = this.onOpened.bind(this);
this.onRegistered = this.onRegistered.bind(this);
this.onIds = this.onIds.bind(this);
OneSignal.addEventListener('received', this.onReceived);
OneSignal.addEventListener('opened', this.onOpened);
OneSignal.addEventListener('registered', this.onRegistered);
OneSignal.addEventListener('ids', this.onIds);
}