Navgigation.navigate(param) with PushNotification in react native and react navigation - push-notification

I want to redirect my user on the page I choose when they click on my push notification. I know how to get parameters from firebase.
Push Notification package : https://github.com/zo0r/react-native-push-notification
React navigation : https://reactnavigation.org/docs/getting-started/
Index.tsx :
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import PushNotification from "react-native-push-notification";
import { useNavigation } from '#react-navigation/native';
PushNotification.configure({
onNotification: function (notification) {
console.log(notification)
screenToRedirect = notification.data.screen //from firebase key => value
//here I'd like to do something like this :
const navigation = useNavigation();
navigation.navigate(screenToRedirect )
},
requestPermissions: Platform.OS === 'ios'
});
AppRegistry.registerComponent(appName, () => App);
It tells me:
Invalid hook call. Hooks can only be called inside of the body of a function component. Problem : I can't put the PushNotification.configure inside a component (it is mentioned in the doc).

You can't use hooks outside react components. You can try this solution: https://reactnavigation.org/docs/navigating-without-navigation-prop/

SOLUTION
I just added my principal menu in the root navigation. So the navigationRef return my principal menu, example :
export const Drawer = createDrawerNavigator();
export const MyDrawer = () => {
return(
<Drawer.Navigator initialRouteName="Accueil">
<Drawer.Screen name="Accueil" component={Home} />
<Drawer.Screen name="Qui sommes-nous ?" component={Who} />
<Drawer.Screen name="Nos Services" component={Services} />
<Drawer.Screen name="Nos Biens" component={Bien} />
</Drawer.Navigator>
)
}
export const Bottom = createBottomTabNavigator();
export const MyBottom = () => {
return(
<Bottom.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused
? 'home'
: 'folder';
} else if (route.name === 'Mon Profil') {
iconName = focused ? 'alert-circle' : 'aperture-sharp';
}
// You can return any component that you like here!
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
})}
>
<Bottom.Screen name="Home" component={MyDrawer} options={{ headerShown: false }}/>
<Bottom.Screen name="Mon Profil" component={Profil} options={{ headerShown: false }}/>
</Bottom.Navigator>
)
}
export const navigationRef = createNavigationContainerRef()
export function navigate(name, params) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params);
}
}
const RootStack = createNativeStackNavigator();
export default function AppMenu() {
return (
<NavigationContainer ref={navigationRef} independent={true}>
<RootStack.Navigator initialRouteName="Accueil">
// Here, I return My Bottom which is the principal menu that return itself MyDrawer menu
<RootStack.Screen name="Home2" component={MyBottom} options={{ headerShown: false }}/>
</RootStack.Navigator>
</NavigationContainer>
);
}
And now My App.tsx return AppMenu.

Here's how I solved this problem:
1- First create a RootNavigation,js file
2- Add the below code in that file
import {createNavigationContainerRef} from '#react-navigation/native';
export const navigationRef = createNavigationContainerRef();
export function navigate(name, params) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params);
}
}
3- Add navigationRef in navigation container
import {navigationRef} from './RootNavigation';
<NavigationContainer ref={navigationRef}>
<MainStack />
</NavigationContainer>
4- Now you can use this ref outside of NavigationContainer as well if you have initialized your push notifications in app.js
import * as RootNavigation from './src/navigation/RootNavigation';
***if you are using one signal for push notifications then you can navigate to your screen in setNotificationOpenedHandler method***
//Method for handling notifications opened
OneSignal.setNotificationOpenedHandler(notification => {
RootNavigation.navigate("your_screen_name");
});

Related

wrapper.getInitialPageProps from next-redux-wrapper don't return the props to the component

I just updated my next-redux-wrapper package and did some changes according to their latest doc. However, the component didn't receive the props returned from MyComponent.getInitialProps = wrapper.getInitialPageProps(store => (context) => ({ foo: "bar }))
When I console the props on MyComponent, there is no "foo"
Has anyone solved this or know why this happens?
I tried to change my __app.js and use getInitialProps without wrapper.getInitialPageProps, but it changes nothing. Here is my __app.js
import { wrapper } from 'store';
import { Provider } from 'react-redux';
import App from 'next/app';
const MyApp = ({ Component, ...rest }) => {
const { store, props } = wrapper.useWrappedStore(rest);
return (
<Provider store={store}>
<Component {...props.pageProps} />
</Provider>
);
};
MyApp.getInitialProps = wrapper.getInitialAppProps(
(store) => async (appCtx) => {
const appProps = await App.getInitialProps(appCtx);
return {
pageProps: {
...appProps.pageProps,
},
};
}
);
export default MyApp;

React Native : Navigate to a screen "nested inside BottomTabNavigator" from firebase push notificaiton click

This is a part of my BottomNavigator code in App.js.
const Bottom = createBottomTabNavigator();
return (
<Bottom.Navigator
tabBarOptions={...}>
<Bottom.Screen
name="ScreenA"
component={ScreenA}
options={...}
/>
<Bottom.Screen
name="ScreenB"
component={ScreenB}
options={...}
/>
<Bottom.Screen
name="ScreenC"
component={ScreenC}
options={...}
/>
<Bottom.Screen
name="Chat"
component={Chat}
options={({navigation}) => ({
tabBarLabel: StringsOfLanguages.chat,
tabBarIcon: ({focused, color, size}) =>
focused ? (
<Image
style={appStyles.bottomTabImgSize}
source={require('./assets/abc.svg')}
/>
) : (
<Image
style={appStyles.bottomTabImgSize}
source={require('./assets/def.svg')}
/>
),
tabBarButton: (props) =>
<TouchableOpacity
{...props}
onPress={() =>
navigation.navigate('Chat', {screen: 'ChatSubA'})
}
/>
})}
/>
</Bottom.Navigator>
);
and this is the code for bottom tab named "Chat"
const Chat = () => {
// Usually ChatSubB is called from ChatSubA.. But on receiving push notification
// ChatSubB should be directly loaded.
<Stack.Navigator initialRouteName="ChatSubA">
<Stack.Screen
name="ChatSubA"
component={ChatSubA}
options={{headerShown: false}}
/>
<Stack.Screen
name="ChatSubB"
component={ChatSubB}
options={{headerShown: false}}
/>
<Stack.Screen
name="ChatSubC"
component={ChatSubC}
options={{headerShown: false}}
/>
<Stack.Screen
name="ChatSubD"
component={ChatSubD}
options={{headerShown: false}}
/>
</Stack.Navigator>
);};
Say if I want to navigate to 'ChatSubB' screen from ScreenA/ScreenB/ScreenC I am using the code
props.navigation.navigate(Chat, {
screen: ChatSubB,
params: {param1:'hai'},
});
But now I need to call 'ChatSubB' on push notification onclick
I don't have 'props' or 'navigate' available to call the above line of code.
This is my PushNotificationHelper.js file. I call these methods from App.js useEffect()
import messaging from '#react-native-firebase/messaging';
import AsyncStorage from '#react-native-async-storage/async- storage';
export async function requestUserPermission() {
...
await getFcmToken();}
export async function getFcmToken() {
...}
export const notificationListener = (navigate) => {
messaging().onNotificationOpenedApp(remoteMessage => {
console.log(
'Notification caused app to open from background state:',
remoteMessage.notification,
);
// navigate("ChatScreen",{
// result: "2371820992-5406-07082-13972-17488760826513",
// });
// navigate('ChatScreen', {
// result: "2371820992-5406-07082-13972-17488760826513",
// });
navigate("Others", {
screen: ChatScreen,
params: {result: "2371820992-5406-07082-13972-17488760826513"},
});
});}
While refering to obtain props or navigate I found a solution using createRef
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
and use
RootNavigation.navigate('ChatSubB ', {param1:'hai'})
But this code doesn't work for me as "ChatSubB" is nested in BottomTabNavigator
tab "Chat".
Is there any other solution to achieve my requirement?
Any help would be grateful..
As explained in the docs you can navigate like this -
RootNavigation.navigate('ChatSubB', {
screen: 'Chat',
params: {
param1:'hai'
}
});
Maybe this documentation may help you?
https://reactnavigation.org/docs/nesting-navigators/#nesting-multiple-navigators
And try to use useNavigation hook)

Expo ReactNative Firebase Get authenticated user and profile in one go

I am new to Expo and ReactNative. At the moment, I am developing a mobile app that utilizes Firebase Auth and Firestore for authentication and user profile. From googling, I found a solution to use ContextProvider for the Firebase Auth. I tried to adjust the code to obtain the profile at the same time.
This is the code for AuthenticatedUserProvider.js
import React, { useState, createContext } from 'react';
export const AuthenticatedUserContext = createContext({});
export const AuthenticatedUserProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [profile, setProfile] = useState(null);
return (
<AuthenticatedUserContext.Provider value={{ user, setUser, profile, setProfile }}>
{children}
</AuthenticatedUserContext.Provider>
);
};
And I read the profile on the RotNavigator.js below
import React, { useContext, useEffect, useState } from 'react';
import { View, ActivityIndicator } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import * as eva from '#eva-design/eva';
import { ApplicationProvider, IconRegistry } from '#ui-kitten/components';
import { EvaIconsPack } from '#ui-kitten/eva-icons';
import { auth, fs } from '../config/firebase';
import { AuthenticatedUserContext } from './AuthenticatedUserProvider';
import AuthStack from './AuthStack';
import HomeStack from './HomeStack';
import { default as theme } from '../orange-theme.json';
export default function RootNavigator() {
const { user, setUser, profile, setProfile } = useContext(AuthenticatedUserContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// onAuthStateChanged returns an unsubscribe
const unsubscribeAuth = auth.onAuthStateChanged(async authenticatedUser => {
try {
if (authenticatedUser) {
await setUser(authenticatedUser)
const response = await fs.collection("members").doc(authenticatedUser.uid).get();
if (response.exists) {
let data = response.data();
// console.log(data);
await setProfile(data);
// console.log(profile);
}
} else {
await setUser(null);
await setProfile(null);
}
setIsLoading(false);
} catch (error) {
console.log(error);
}
});
// unsubscribe auth listener on unmount
return unsubscribeAuth;
}, []);
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size='large' />
</View>
);
}
return (
<>
<IconRegistry icons={EvaIconsPack} />
<ApplicationProvider {...eva} theme={{ ...eva.light, ...theme }}>
<NavigationContainer>
{user ? <HomeStack /> : <AuthStack />}
</NavigationContainer>
</ApplicationProvider >
</>
);
}
The code looks like working except that if I log in, sometimes I saw an error on profile = null and cannot read the profile but then it refreshed by itself and back to run properly.
Can someone suggest the correct way to display the profile from Firestore?
The reason I do it like this is I want an efficient way of displaying the profile since HomeStack has 3 screens that would need to display some parts of the profile on each screen.
Thank you for your help.

How to add firebase screen tracking analytics with React Native React Navigation when using createStackNavigator?

I have a React Native app and am using React Navigation. I am now trying to add screen tracking analytics with firebase.
I am following this documentation, which has this sample code:
import analytics from '#react-native-firebase/analytics';
import { NavigationContainer } from '#react-navigation/native';
<NavigationContainer
ref={navigationRef}
onStateChange={state => {
const previousRouteName = routeNameRef.current;
const currentRouteName = getActiveRouteName(state);
if (previousRouteName !== currentRouteName) {
analytics().setCurrentScreen(currentRouteName, currentRouteName);
}
In my code, however, I am creating my base NavigationContainer with a function like so:
export default createStackNavigator(
{
Home: MainTabNavigator,
SignIn: SignInNavigator,
},
{
transitionConfig: dynamicModalTransition,
headerMode: 'none',
initialRouteName: 'Home',
},
);
What is the best way to integrate the code from the example?
The problem is because you are on react-navigation v4.x.x, but the example you have is for v5.x.x.
In v4, event listeners can be added on AppContainer.
The example below is for v4.
import React from 'react';
import { createAppContainer, createStackNavigator } from 'react-navigation';
function getActiveRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
if (route.routes) {
return getActiveRouteName(route);
}
return route.routeName;
}
const nav = createStackNavigator({...});
const AppContainer = createAppContainer(nav);
export default () => {
return <AppContainer
onNavigationStateChange={(prevState, currentState, action) => {
const currentRouteName = getActiveRouteName(currentState);
const previousRouteName = getActiveRouteName(prevState);
if (previousRouteName !== currentRouteName) {
analytics().setCurrentScreen(currentRouteName, currentRouteName);
}
}}
/>
}
I'm using NavigationContainer and createStackNavigator, too and this is how I did it, like in the example for screen tracking at reactnavigation.org
import * as Analytics from 'expo-firebase-analytics';
import { useRef } from 'react';
import { NavigationContainer } from '#react-navigation/native';
export default () => {
const navigationRef = useRef();
const routeNameRef = useRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() =>
(routeNameRef.current = navigationRef.current.getCurrentRoute().name)
}
onStateChange={async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.current.getCurrentRoute().name;
if (previousRouteName !== currentRouteName) {
// The line below uses the expo-firebase-analytics tracker
// https://docs.expo.io/versions/latest/sdk/firebase-analytics/
// Change this line to use another Mobile analytics SDK
await analytics().logScreenView({
screen_name: currentRouteName,
screen_class: currentRouteName
});
}
// Save the current route name for later comparison
routeNameRef.current = currentRouteName;
}}
>
{/* ... */}
</NavigationContainer>
);
};

React Native: Dynamically change style with AsyncStorage and States

I want to implement dark mode and black mode in my app and the way I have it is that the user toggles on dark/black mode from one class in which I want the state to be updated to all the classes, the toggle class is as followed:
AppearanceToggle Class
state = {
BlackModeValue: null,
DarkModeValue: null
};
componentDidMount = () => {
AsyncStorage.getItem('DarkModeValue').then(value => this.setState({ DarkModeValue: JSON.parse(value) }));
AsyncStorage.getItem('BlackModeValue').then(value => this.setState({ BlackModeValue: JSON.parse(value) }));
};
//AsyncStorage.setItem .........
render() {
return (
<ScrollView style={[ styles.View , this.state.DarkModeValue ? darkmode.ASView : null || this.state.BlackModeValue ? blackmode.ASView : null ]}>
<SettingsList borderColor='#c8c7cc' defaultItemSize={50}>
<SettingsList.Item
hasSwitch={true}
switchState={this.state.DarkModeValue}
switchOnValueChange={//Goes to asyncStorage.setItem method}
title='Dark Mode'
/>
<SettingsList.Item
hasSwitch={true}
switchState={this.state.BlackModeValue}
switchOnValueChange={//Goes to asyncStorage.setItem method}
title='Black Mode'
/>
</SettingsList>
</ScrollView>
);
}
}
And then in the class (which is SettingsScreen.js, this is the screen that navigates to AppearanceToggle ) that I want to .getItem and change the state is as followed:
state = {
switchValue: false,
rated: false,
DarkModeValue:null,
BlackModeValue:null,
};
componentDidMount = () => {
AsyncStorage.getItem('DarkModeValue').then(value => this.setState({ DarkModeValue: JSON.parse(value) }));
AsyncStorage.getItem('BlackModeValue').then(value => this.setState({ BlackModeValue: JSON.parse(value) }));
};
render() {
return (
<ScrollView style={[ styles.View , this.state.DarkModeValue ? styles.DMView : null || this.state.BlackModeValue ? styles.BMView : null ]}>
..........
</ScrollView>
The problem I have is that when I change the switch, it affects the AppearanceToggleScreen Class instantly but not the SettingsScreen UNLESS I refresh the app. Is there a way to do it so all of them get affected instantly?
Perhaps the best way to propagate it is to listen for the changes in your AppComponent using Context or root component. e.g.
So you would create a theme context like :
export const themes = {
blackMode: {
foreground: '#000000',
background: '#eeeeee',
},
darkMode: {
foreground: '#2f4f4ff',
background: '#222222',
},
};
export const ThemeContext = React.createContext(
themes.darkMode // default value
)
;
Your AppearanceToggle class would have something like :
import {ThemeContext} from './theme-context';
class ThemedButton extends Component {
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
And then your AppComponent could be
import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
function Toolbar(props) {
// Render your customized toolbar here and bind the changeTheme function to it
}
class App extends Component {
constructor(props) {
super(props);
};
componentDidMount = () => {
AsyncStorage.getItem('selectedTheme').then(value => this.setState({ selectedTheme: JSON.parse(value) }));
};
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.darkMode
? themes.blackMode
: themes.darkMode,
}));
};
}
render() {
// The ThemedButton button inside the ThemeProvider
// uses the theme from state while the one outside uses
// the default dark theme
return (
<Page>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<Section>
<ThemedButton />
</Section>
</Page>
);
}
}
For more read
The thing is that in the AppearanceToggleScreen you're changing the state, therefore the component is rerendered (with the new theme), but because the SettingsScreen is already in the navigation stack (because that's where you're navigating from) the componentDidMount is not executing again.
Now, maybe you want to use the Context API to access globally to the values, or do something like this.

Resources