How to pass props to react navigation in react native app - react-native

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

Related

React Navigation Navigate from Firebase Push Notification

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

How to use hook with SwitchNavigator

I'm trying to use https://github.com/expo/react-native-action-sheet with switch navigator.
I'm not sure how to do the basic setup as the example in the readme is different than my App.js. I'm using react 16.8 so I should be able to use hooks.
My App.js
import { useActionSheet } from '#expo/react-native-action-sheet'
const AuthStack = createStackNavigator(
{ Signup: SignupScreen, Login: LoginScreen }
);
const navigator = createBottomTabNavigator(
{
Feed: {
screen: FeedScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
Profile: {
screen: ProfileScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
},
);
const stackNavigator = createStackNavigator(
{
Main: {
screen: navigator,
// Set the title for our app when the tab bar screen is present
navigationOptions: { title: 'Test' },
},
// This screen will not have a tab bar
NewPost: NewPostScreen,
},
{
cardStyle: { backgroundColor: 'white' },
},
);
export default createAppContainer(
createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: stackNavigator,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
const { showActionSheetWithOptions } = useActionSheet();
);
Update, I'm getting this error when calling the showActionSheetWithOptions inside my component:
Hooks can only be called inside the body of a function component. invalid hook call
This is my code:
import React, { Component } from 'react';
import { useActionSheet } from '#expo/react-native-action-sheet'
export default class NewPostScreen extends Component {
_onOpenActionSheet = () => {
const options = ['Delete', 'Save', 'Cancel'];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 2;
const { showActionSheetWithOptions } = useActionSheet();
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
buttonIndex => {
console.log(buttonIndex);
},
);
};
render () {
return (
<View>
<Button title="Test" onPress={this._onOpenActionSheet} />
</View>
)
}
}
update 2
I also tried using a functional component, but the actionsheet does not open (console does print "pressed")
// ActionSheet.js
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { useActionSheet } from '#expo/react-native-action-sheet'
export default function ActionSheet () {
const { showActionSheetWithOptions } = useActionSheet();
const _onOpenActionSheet = () => {
console.log("pressed");
const options = ['Delete', 'Save', 'Cancel'];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 2;
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
(buttonIndex) => {
console.log(buttonIndex);
},
);
};
return (
<TouchableOpacity onPress={_onOpenActionSheet} style={{height: 100,}}>
<Text>Click here</Text>
</TouchableOpacity>
);
};
Problem
As you can see here. You are not connecting your application root component.
Solution
import connectActionSheet from #expo/react-native-action-sheet and connect your application root component to the action sheet.
Simply modify your App.js to reflect the following:
// ... Other imports
import { connectActionSheet } from '#expo/react-native-action-sheet'
const AuthStack = createStackNavigator({
Signup: SignupScreen,
Login: LoginScreen
});
const navigator = createBottomTabNavigator({
Feed: {
screen: FeedScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
Profile: {
screen: ProfileScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
});
const stackNavigator = createStackNavigator({
Main: {
screen: navigator,
// Set the title for our app when the tab bar screen is present
navigationOptions: { title: 'Test' },
},
// This screen will not have a tab bar
NewPost: NewPostScreen,
}, {
cardStyle: { backgroundColor: 'white' },
});
const appContianer = createAppContainer(
createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
App: stackNavigator,
Auth: AuthStack,
}, {
initialRouteName: 'AuthLoading',
})
);
const ConnectApp = connectActionSheet(appContianer);
export default ConnectApp;
Now on any of your application screens (i.e. Feed, Profile, Main, etc.) you can access the action sheet as follows:
If Stateless Component
// ... Other imports
import { useActionSheet } from '#expo/react-native-action-sheet'
export default function Profile () {
const { showActionSheetWithOptions } = useActionSheet();
/* ... */
}
If Statefull Component
// ... Other imports
import React from 'react'
import { useActionSheet } from '#expo/react-native-action-sheet'
export default Profile extends React.Component {
const { showActionSheetWithOptions } = useActionSheet();
/* ... */
}
Note: You can also access the action sheet as stated below from the docs
App component can access the actionSheet method as this.props.showActionSheetWithOptions

How to pass login credentials to initial screen of DrawerMenu of navigation drawer in React Native?

I have created React-navigation Drawer V3 in my app. I am facing the problem of getting the login credentials on the initial screen that is the "Home" screen of my drawer navigation stack. Although I am passing the data on DrawerMenu.js from Login.js with the help of :
this.props.navigation.navigate('DrawerMenu', {
loginData: loginData
})
and getting the data on DrawerMenu.js with :
constructor(props) {
super(props)
this.state = {
loginData: props.navigation.state.params.loginData
}
}
But I need to get this data to Home.js which I am not able to get. The reason of getting the data to DrawerMenu.js was because there are several other screens in the stack which want the logindata and they are able to get it using screenProps used in drawer content component but as Home is the initial screen so there no way that it receives the data on the first time, only the DrawerMenu.js file receives it initially. Can someone help me out on this?
Here is my code of DrawerMenu with navigation stack.
class DrawerMenu extends Component {
constructor(props) {
super(props)
this.state = {
loginData: props.navigation.state.params.loginData
}
}
static navigationOptions =
{
header: null,
};
render() {
return (
<SafeAreaView style={styles.droidSafeArea}>
<MyApp screenProps={{ loginData: this.state.loginData}} />
</SafeAreaView>
)
}
}
class Hidden extends React.Component {
render() {
return null;
}
}
const MainScreenNavigator = createStackNavigator({
Home: { screen: Home },
Screen1: {
screen: Screen1
},
Screen2: {
screen: Screen2
},
Screen3: {
screen: Screen3
}
},
{
initialRouteName: 'Home',
});
const MyDrawerNavigator = createDrawerNavigator(
{
Main: {
screen: MainScreenNavigator,
navigationOptions: {
drawerLabel: <Hidden />
}
}
},
{
drawerWidth: 300,
contentComponent: DrawerComponent
});
const MyApp = createAppContainer(MyDrawerNavigator);
export default DrawerMenu;
I am using :
"react-native" : "0.57.5",
"react": "16.6.1",
"react-navigation": "^3.5.1"
You can use AsyncStorage to save the data
To store
_storeData = async () => {
try {
await AsyncStorage.setItem('Key Value', 'Item Value');
} catch (error) {
// Error saving data
}
};
To retrieve
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('Key Value');
if (value !== null) {
// We have data!!
console.log(value);
}
} catch (error) {
// Error retrieving data
}
};
Or you can use redux.

How to use AsyncStorage in createBottomTabNavigator with React Navigation?

I work with react-navigation v3 and I want to use AsyncStorage in createBottomTabNavigator for checking if user logged.
I save key to Stoage in LoginScreen:
await AsyncStorage.setItem('#MyStorage:isLogged', isLogged);
And I want to use AsyncStorage in my stack (TabStack):
const TabStack = createBottomTabNavigator(
{
Home: { screen: HomeScreen, },
// I need isLogged key from AsyncStorage here!
...(false ? {
Account: { screen: AccountScreen, }
} : {
Login: { screen: LoginScreen, }
}),
},
{
initialRouteName: 'Home',
}
);
How I can do it?
My environment:
react-native: 0.58.5
react-navigation: 3.3.2
The solution is: create a new component AppTabBar and set this in tabBarComponent property
const TabStack = createBottomTabNavigator({
Home: { screen: HomeScreen, },
Account: { screen: AccountScreen, }
},
{
initialRouteName: 'Home',
tabBarComponent: AppTabBar, // Here
});
And AppTabBar component:
export default class AppTabBar extends Component {
constructor(props) {
super(props);
this.state = {
isLogged: '0',
};
}
componentDidMount() {
this._retrieveData();
}
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('isLogged');
if (value !== null) {
this.setState({
isLogged: value,
});
}
} catch (error) {
// Error retrieving data
}
};
render() {
const { navigation, appState } = this.props;
const routes = navigation.state.routes;
const { isLogged } = this.state;
return (
<View style={styles.container}>
{routes.map((route, index) => {
if (isLogged === '1' && route.routeName === 'Login') {
return null;
}
if (isLogged === '0' && route.routeName === 'Account') {
return null;
}
return (
<View /> // here your tabbar component
);
})}
</View>
);
}
navigationHandler = name => {
const { navigation } = this.props;
navigation.navigate(name);
};
}
You don't need to do that, just may want to check for a valid session in the login screen.
You need to create 2 stacks, one for the auth screens and your TabStack for logged users:
const TabStack = createBottomTabNavigator({
Home: { screen: HomeScreen, },
Account: { screen: AccountScreen, }
},
{
initialRouteName: 'Home',
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
});
const stack = createStackNavigator({
Home: {screen: TabStack},
Login: { screen: LoginScreen, }
});
and then check for a valid session in LoginScreen in the method componentDidMount.
class LoginScreen extends Component {
componentDidMount(){
const session = await AsyncStorage.getItem('session');
if (session.isValid) {
this.props.navigate('home')
}
}
}
In your Loading screen, read your login state from AsyncStorage, and store it in your Redux store, ( or any sort of global data sharing mechanism of your choice ) - I'm using redux here, then read this piece of data in your Stack component like the following:
import React from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import { createStackNavigator } from "react-navigation";
class Stack extends React.Component {
render() {
const { isLoggedIn } = this.props.auth;
const RouteConfigs = {
Home: () => (
<View>
<Text>Home</Text>
</View>
),
Login: () => (
<View>
<Text>Login</Text>
</View>
)
};
const RouteConfigs_LoggedIn = {
Home: () => (
<View>
<Text>Home</Text>
</View>
),
Account: () => (
<View>
<Text>Account</Text>
</View>
)
};
const NavigatorConfig = { initialRouteName: "Login" };
const MyStack = createStackNavigator(
isLoggedIn ? RouteConfigs_LoggedIn : RouteConfigs,
NavigatorConfig
);
return <MyStack />;
}
}
const mapStateToProps = ({ auth }) => ({ auth });
export default connect(mapStateToProps)(Stack);

react-navigation StackNavigator reset when update Redux state

need some help here, i having some problem when using redux with react navigation.
Whenever i update state in redux, the react-navigation will reset to initialRouteName in my DrawerNavigator which is Home
How can i stay on that screen after this.props.dispatch to update Redux state?
Is there any step that i should do when integrate redux with react-navigation?
Thank you so much for any help. Appreciate it
App.js
This is where i declare my StackNavigator, my DrawerNavigator is
nested in StackNavigator
import React, { Component } from "react";
import { connect, Provider } from "react-redux";
import { Platform, BackHandler, View, Text } from "react-native";
import { Root, StyleProvider, StatusBar } from "native-base";
import { StackNavigator, NavigationActions } from "react-navigation";
import Drawer from "./Drawer";
import AuthNavigator from "./components/login/authNavigator";
import Home from "./components/home";
import Settings from "./components/settings";
import UserProfile from "./components/userProfile";
import getTheme from "../native-base-theme/components";
const AppNavigator = token => {
return StackNavigator(
{
Drawer: { screen: Drawer },
Login: { screen: Login },
Home: { screen: Home },
AuthNavigator: { screen: AuthNavigator },
UserProfile: { screen: UserProfile }
},
{
initialRouteName: token ? "Drawer" : "AuthNavigator",
stateName: "MainNav",
headerMode: "none"
}
);
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
isReady: false
};
}
componentDidMount() {
setTimeout(() => {
this.setState({ isReady: true });
}, 500);
}
render() {
let token = null;
const users = this.props.auth.users;
const index = this.props.auth.defaultUserIndex;
if (users.length > 0) {
token = users[index].token;
}
const Layout = AppNavigator(token);
return (
<Root>
<StyleProvider style={getTheme()}>{this.state.isReady ? <Layout /> : <View />}</StyleProvider>
</Root>
);
}
}
var mapStateToProps = state => {
return {
auth: state.auth
};
};
module.exports = connect(mapStateToProps)(App);
Drawer.js
This is my Drawer, whenever i update Redux state, the app will pop back
to the initialRouteName of DrawerNavigator which is Home
import React from "react";
import { DrawerNavigator, StackNavigator } from "react-navigation";
import { Dimensions } from "react-native";
import SideBar from "./components/sidebar";
import Home from "./components/home/";
import Settings from "./components/settings/";
const deviceHeight = Dimensions.get("window").height;
const deviceWidth = Dimensions.get("window").width;
const Drawer = DrawerNavigator(
{
Home: { screen: Home },
Settings: { screen: Settings },
CompanyProfile: { screen: CompanyProfile }
},
{
initialRouteName: "Home",
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
drawerWidth: deviceWidth - 100
}
);
export default Drawer;
Reducer.js
const defaultState = {
users: [],
defaultUserIndex: 0
};
const defaultUserState = {
phoneNumber: undefined,
email: undefined,
name: undefined,
userId: undefined,
token: undefined,
avatar: undefined
};
module.exports = (state = defaultState, action) => {
console.log("reducer state: ", state);
switch (action.type) {
case "AUTH_USER":
case "UNAUTH_USER":
case "UPDATE_AVATAR":
case "UPDATE_PHONENUMBER":
case "UPDATE_PERSONALDETAILS":
return { ...state, users: user(state.defaultUserIndex, state.users, action) };
case "CLEAR_STATE":
return defaultState;
default:
return state;
}
};
function user(defaultUserIndex, state = [], action) {
const newState = [...state];
switch (action.type) {
case "AUTH_USER":
return [
...state,
{
phoneNumber: action.phoneNumber,
name: action.name,
email: action.email,
userId: action.userId,
token: action.token,
avatar: action.avatar,
}
];
case "UNAUTH_USER":
return state.filter(item => item.token !== action.token);
case "UPDATE_AVATAR":
newState[defaultUserIndex].avatar = action.avatar;
return newState;
case "UPDATE_PERSONALDETAILS":
newState[defaultUserIndex].name = action.name;
newState[defaultUserIndex].email = action.email;
return newState;
default:
return state;
}
}
In your case: you create new AppNavigator on each render invoke. And each of instance have new navigation state.
You can fix it by moving initialization to constructor, so every next render it will use same object.
constructor(props) {
super(props);
this.state = {
isReady: false
};
const token = //sometihng
this.navigator = AppNavigator(token)
}
Also: explore examples of redux integration in official docs https://github.com/react-community/react-navigation/blob/master/docs/guides/Redux-Integration.md