Navigate app from the application root - react-native

I'm trying to accomplish receiving a push notificaiton and then navigating my app to a specific route depending on the push notification. Everything with the push notification is working correctly, I'm just having an issue of how to navigate my app from the application root, or maybe I need to take a different approach.
// root component that hooks up to redux and renders the app
class Root extends Component {
render() {
return (
<Provider store={store}>
<App />
</Provider>
)
}
}
// app component that just renders the navigated application
class App extends Component {
componentWillMount () {
this._notificationSubscription = Notifications.addListener(this.handleNotification)
}
handleNotification = notification => {
// handler for when a push notification is received
if (notification.origin === 'selected') {
// this is where I need to navigate ]
console.log(navigation.data)
}
}
render() {
return <NavigatedApp />
}
}
The handler for the push notification works and I have the data available. I need to perform something like this.props.navigation.navigate('PageDetail', { pageId }) but obviously the navigation property isn't available at this point because it's not inside of the AppNavigator component. I've tried hooking up to redux, however I still get the same issue where the navigation dispatch action isn't available until entereing inside the AppNavigator.
Any ideas how I could perform a navigate from the App component or maybe a different approach?

I don't have the whole picture of your scenario so this might not be a hundred percent fitting but that does look like a case of having to call navigate on a top level component?
[...]
import { NavigationActions } from 'react-navigation';
[...]
// app component that just renders the navigated application
class App extends Component {
componentWillMount () {
this._notificationSubscription = Notifications.addListener(this.handleNotification)
}
handleNotification = notification => {
// handler for when a push notification is received
if (notification.origin === 'selected') {
// this is where I need to navigate ]
console.log(navigation.data)
this.navigator && this.navigator.dispatch(
NavigationActions.navigate({ routeName: someRouteName })
);
}
}
render() {
return <NavigatedApp ref={nav => { this.navigator = nav; }} />
}
}

Related

React native How to execute function every time when i open page

I need to send request every time when i open page. Currently when i access page first time after load the app everything is ok, but if i go to another page and back after that request is not send it again.
You have to add focus listener so when you go back, It will refresh the data like
import * as React from 'react';
import { View } from 'react-native';
function AppScreen({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// The screen is focused
// Call any action and update data
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation]);
return <View />;
}
source : https://reactnavigation.org/docs/function-after-focusing-screen/
Here you go, example for a class based and functional based component to run something on every load of the screen.
import React, { useEffect } from "react";
import {View} from 'react-native'
//Functional Component
const App = () =>
{
useEffect(() =>
{
myAction();
}, [])
return (
<View>
</View>
);
}
//Class based Component
class App extends Component
{
componentDidMount()
{
this.myAction();
}
render()
{
return(
<View>
</View>
)
}
}

Queue navigation until screen is mounted and then navigate

I am trying to navigate to a certain screen on my bottom-tab-navigator when a user opens the app by clicking a notification.
Looking into the official docs Navigating without the navigation prop, my setup of my main navigator is as follows:
import {navigationRef, isReadyRef} from './root';
const MainNav = _ => {
if (isLoading) {
return isFirstTime ? (<OnBoarding />) : (<SplashScreen />);
}
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {isReadyRef.current = true}}>
{!token ? <AuthNav /> : <AppNav />}
</NavigationContainer>
);
}
My root.js is as follows:
import * as React from 'react';
export const isReadyRef = React.createRef();
export const navigationRef = React.createRef();
export function navigate(name, params) {
if (isReadyRef.current && navigationRef.current) {
// Perform navigation if the app has mounted
navigationRef.current.navigate(name, params);
} else {
// You can decide what to do if the app hasn't mounted
// You can ignore this, or add these actions to a queue you can call later
console.log('Not mounted yet.')
}
}
And I had added the OneSignal event listener in my root index.js as following:
const App = _ => {
useEffect(() => {
OneSignal.addEventListener('opened', onOpened);
return () => OneSignal.removeEventListener('opened', onOpened);
}, []);
return {
<StoreProvider store={store}>
<MainNav />
</StoreProvider>
}
}
And my onOpened function is as follows:
import {navigate} from '../nav/root';
const onOpened = ({notification}) => {
if(notification.type == 'New Request'){
navigate('Notifications');
}
}
But when I test it as expected Not mounted yet. is printed to console. So I want to
add these actions to a queue you can call later
as stated by the official react navigation docs but I am not sure how to do this. I found react-native-queue but it is no longer being maintained and using a setTimeout just seems like an ugly hack cause the load time varies. So is there a better approach or solution that I can use to navigate only after the loading is done (I am thinking of using redux for this) and my navigators have been mounted (not sure how to do this)?

Defer notification deep linking until after react-native expo app is fully loaded

I would like to defer the deep-link navigation event tied to a notification until the app is fully loaded after my react-native app is opened on notification click.
Currently, my notification listener is in my App.tsx file. The deep linking works as expected when the app is backgrounded, but when the notification triggers the app to open, the navigation event is kicked off before the App has a chance to fully load. This means that although I do get deep linked to the correct location, some of my assets aren't yet loaded and my auth logic is all bypassed.
Is there a way to have a notification open the app, but wait until everything is loaded (specifically the AppLoading component has finished running its functions) to navigate to the deep linking location? I can think of some hack-y seeming ways to do this but is there an established pattern that is commonly used?
Ok so I'm not sure if this is the best way to handle this but I found a way to get my notifications working.
(1) I created an action called registerAppLoaded and a state variable appLoaded that I dispatch once my AppLoading component finishes its startAsync functions.
(2) When a notification comes in, I first check to see if appLoaded is true. If so, I navigate to the destination sent along with the notification. If not, I put the notification in the store and carry on with firing up the app.
(3) in the AppLoading onFinish function, I check to see if there is a notification. If so, and it is marked new, I grab it and use the params to navigate. Then I dispatch an action that sets navigation.new = false
Seems to work exactly the way i need it to!
Here is my code if anyone else is dealing with this issue:
App.tsx:
export default class App extends React.Component {
componentDidMount() {
Notifications.addListener((notification) => {
if (notification.origin === "received") {
return;
} else {
const appLoaded = store.getState().general.appLoaded
if (appLoaded) {
NavigationService.navigate(notification.data.navigate, {id:notification.data.id , shouldRefresh: true})
} else {
// save notification as new
store.dispatch(addNotification(notification, true));
}
};
})
}
render() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<View style={styles.container}>
{Platform.OS === "ios" && <StatusBar barStyle="default" />}
<ErrorBoundary backup={<AppError />}>
<AppNavigator />
</ErrorBoundary>
</View>
</PersistGate>
</Provider>
);
}
}
AppLoading.tsx:
class AppLoadingScreen extends React.Component {
_loadResourcesAsync = async() => {
await this.props.fetchUserOnboarding(this.props.userId);
if (this.props.userDevice && this.props.userDevice.id) {
this.props.registerUserDevice(this.props.userId, this.props.userDevice.id)
}
// register that the app has finished loading in the store; this will be used to determine if a notification's deep
// link should be immediatedly navigated to or if the navigation even shuold be deferred until after the app
// finishes loading
await this.props.registerAppLoaded()
};
_handleFinishLoading = async () => {
// if a notification triggers the event listener but the app is not yet fully loaded, the deep link will be
// navigated to here instead of directly from the listener
const notification = this.props.userNotifications && this.props.userNotifications[0]
if (notification && notification.new && notification.origin != 'recieved') {
this.props.addNotification(notification, false) // set notification.new = false
this.props.navigation.navigate(notification.data.navigate, {id: notification.data.id, shouldRefresh: true})
} else if (this.props.showGoalsPrompt) {
this.props.navigation.navigate("Goal");
} else {
this.props.navigation.navigate("HomeFeed");
}
};
render() {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onFinish={this._handleFinishLoading}
/>
);
}
}
actions.tsx:
export const registerAppLoaded = () => {
return dispatch => {
dispatch({
type: types.REGISTER_APP_LOADED,
payload: true
});
};
}
export const addNotification = (notification, isNew=false) => {
return dispatch => {
notification.new = isNew // indicate whether this is a new notification or if it has been seen
dispatch({
type:types.ADD_USER_NOTIFICATION,
payload: notification,
})
};
}

How to access this.props.navigation in App.js

I want to pass navigation props to a firebase notificationListener in App.js but this.props.navigation is undefined which I can understand as App.js is the root where navigation is initiated.
If there is any workaround it would be great.
Thanks!
class App extends Component {
componentDidMount() {
notificationListener.initNotificationListener(this.props.navigation);
}
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor} loading={null}>
<Navigation />
</PersistGate>
</Provider>
);
}
}
export default App
One way you can workaround not having access to props.navigation in app.js when opening a notification is by setting up a value in your async storage when the app has been opened from a notification
//IN APP.JS
const notificationOpen = await
firebase.notifications().getInitialNotification();
if (notificationOpen) {
this._accessFromNotification();
}
_accessFromNotification = async () => {
console.log("Setting Access from Notification")
await AsyncStorage.setItem('accessFromNot', true);}
After that you can call this variable from your async storage inside the componentDidMount of the first component from your navigation Stack and from there navigate to another component if the variable's value==true.
//IN THE FIRST COMPONENT THAT HAS ACCESS TO PROPS NAVIGATION
componentDidMount() {
this._verifyOpenFromNot()
}
_verifyOpenFromNot = async()=>{
const acc= await AsyncStorage.getItem('accessFromNot');
if (acc){
this.props.navigation.navigate('NotificationViewer');
this._setAccessFalse();
}
}
Finally you should update the async storage setting up the accessFromNot variable to false, to avoid automatic navigation the next time you open the app.
_setEntroDesdeNotFalse = async () => {
await AsyncStorage.setItem('accessFromNot', 'false');}

Navigation.goBack() with an API call in React-Native

In my application, I have few cases where navigation.goBack() cannot be used. I use react-navigation for navigation. When i'm in the detail screen, When I go back, I want to send an API call to get the latest records to the parent screen. So I used, navigation.navigate() instead of navigation.goBack(); But, this makes my app slow if I navigate and navigate back few times. It gets very slow if I do this few more times. What is the reason behind this? How the navigation.navigate() differs from navigation.goBack()?
What is the preferred way of handling this kind of scenario?
is there a way to pass param from navigate.goback() and parent can listen to the params and update its state?
You can pass a callback function as parameter (as mentioned in other answers).
Here is a more clear example, when you navigate from A to B and you want B to communicate information back to A you can pass a callback (here onSelect):
ViewA.js
import React from "react";
import { Button, Text, View } from "react-native";
class ViewA extends React.Component {
state = { selected: false };
onSelect = data => {
this.setState(data);
};
onPress = () => {
this.props.navigate("ViewB", { onSelect: this.onSelect });
};
render() {
return (
<View>
<Text>{this.state.selected ? "Selected" : "Not Selected"}</Text>
<Button title="Next" onPress={this.onPress} />
</View>
);
}
}
ViewB.js
import React from "react";
import { Button } from "react-native";
class ViewB extends React.Component {
goBack() {
const { navigation } = this.props;
navigation.goBack();
navigation.state.params.onSelect({ selected: true });
}
render() {
return <Button title="back" onPress={this.goBack} />;
}
}
Hats off for debrice - Refer to https://github.com/react-navigation/react-navigation/issues/288#issuecomment-315684617