How to access this.props.navigation in App.js - react-native

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');}

Related

How to update state on App start in react native

I am trying to read from the AsyncStorage and update my store ( using easy-peasy ) on app start.
My thought process was to call fetch the data from AsyncStorage using useEffect with the second argument of an empty array to only fetch once on app start and update the store with that value using an action.
But that doesn't work i get the error invalid hook call. Any insights on how to solve this or what the correct approach would be are appreciated !
App.ts
export default function App() {
useEffect(() => {
readData();
}, []);
return (
<StoreProvider store={store}>
<SafeAreaProvider>
<Navigation />
<StatusBar />
</SafeAreaProvider>
</StoreProvider>
);
}
// This needs to be called when app is started
const readData = async () => {
try {
const secret = await storage.getItem('secret');
const initializeState = useStoreActions(
(actions) => actions.initializeState
);
initializeState({
payload: {
secret,
},
});
console.log("executed action")
} catch (e) {
console.log('Failed to fetch the input from storage', e);
}
};
STORE
initializeState: action((state, payload) => {
state.secret = payload.secret
}),
ERROR
Failed to fetch the input from storage [Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.]
export default function App() {
useEffect(() => {
setTimeout(()=>{ // add setTimeout() may be this work for you
readData();
},500)
}, []);
return (
<StoreProvider store={store}>
<SafeAreaProvider>
<Navigation />
<StatusBar />
</SafeAreaProvider>
</StoreProvider>
);
}
// This needs to be called when app is started
const readData = async () => {
try {
const secret = await storage.getItem('secret');
const initializeState = useStoreActions(
(actions) => actions.initializeState
);
initializeState({
payload: {
secret,
},
});
console.log("executed action")
} catch (e) {
console.log('Failed to fetch the input from storage', e);
}
};
You need to move you readData function into the App component since you're using a hook (useStorageActions) inside that function and you can only call hooks at the top level. You should take a look at the rules of react hooks.

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)?

How to get access to reducer inside App using React Navigation v5 in React Native?

I'm trying to build app with React Navigation v5 and stuck with Authentication flow.
Here is some code to understand what I'm trying to do:
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const AuthContext = React.createContext();
export default class App extends Component {
// constructor() ...
render() {
const store = configureStore(); // rootReducer
return (
<AuthContext.Provider store={store}>
<NavigationContainer>
// here I have to access my userReducer to check is user logged in and using LoginStack or Drawer
)
// my stacks
}
So in React Navigation docs Authentication flows uses function components and React Hooks inside them. But I'm using class-component, and I have my reducer in standalone file.
I tried to use connect() as I always do on child components in my app, but this won't work.
So is there any way to access (or map) reducer to App at the topmost level? Or maybe I'm doing something wrong and there is a better way to build switch between 2 separate stacks based on authentication?
Im not sure if this is the best way but you'll just have to use react hooks and redux subscribe:
export default function Navigation({store}) {
const [authorized, setAuthorized] = useState(
store.getState().user.auth !== null,
);
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setAuthorized(store.getState().user.auth !== null);
});
return () => { unsubscribe(); }
});
return (
<NavigationContainer>
{authorized ?
<Stack.Navigator>...</Stack.Navigator> : <Stack.Navigator>...</Stack.Navigator>}
</NavigationContainer>
)
}
Then pass your store:
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Navigation store={store} />
</Provider>
);
}
}

Navigate app from the application root

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; }} />
}
}

Error when trying to acces parent TabNavigator's state inside child TabNavigator

I am currently trying to access the Parent Tab Navigator's state inside child Tab Navigator in the react-navigation while integrating redux into the app as well. But I am getting an error while trying to achieve the same.
Its said in the docs that
Once you do this, your navigation state is stored within your redux store, at which point you can fire navigation actions using your redux dispatch function.
Keep in mind that when a navigator is given a navigation prop, it relinquishes control of its internal state. That means you are now responsible for persisting its state, handling any deep linking, Handling the Hardware Back Button in Android, etc.
Navigation state is automatically passed down from one navigator to another when you nest them. Note that in order for a child navigator to receive the state from a parent navigator, it should be defined as a screen.
So the docs tell us that the state is passed down. So technically I should be able to access them in the childNavigator. But for some reason, I end up in an error.
This is my rootNavigator:
class RootTab extends Component {
render() {
const { dispatch, rootNavigationState } = this.props;
return (
<RootTabConfiguration
rootNavigation={
addNavigationHelpers({
dispatch,
rootState: rootNavigationState,
})
}
/>
);
}
}
const mapStateToProps = (state) => {
return {
rootNavigationState: state.rootTabState,
};
};
export default connect(mapStateToProps)(RootTab);
This is my Child Navigator:
class HomeTab extends Component {
render() {
const { dispatch, homeNavigationState } = this.props;
const { rootState } = this.props.rootNavigation; //This line causes the error
return (
<HomeTabConfiguration
homeNavigation={
addNavigationHelpers({
dispatch,
homeState: homeNavigationState,
rootState
})
}
/>
);
}
}
const mapStateToProps = (state) => {
return {
homeNavigationState: state.homeTabState,
};
};
export default connect(mapStateToProps)(HomeTab);
This is the error I get:
I think I am understanding something wrong. Any ideas/suggestions?