react native this.props.navigation is undefined in main APP - react-native

I'm trying to use "this.props.navigation" in my main react native App but it is undefined. In my others components included with TabNavigator and ScreenNavigator it work.
My App.js
const MyTabView = TabNavigator(
{
Projects: { screen: Projects },
Explorer: { screen: Explorer },
Profil: { screen: Profil }, ...
}
);
const StackView = StackNavigator(
{
global: {
screen: MyTabView,
headerMode: "none",
header: null,
navigationOptions: {
header: null
}
},
TermsOfUse: { screen: TermsOfUse },
LegalNotice: { screen: LegalNotice },
Options: { screen: Options }, ...
}
);
export default class App extends React.PureComponent {
constructor (props) {
super(props);
console.log(this.props.navigation); // <---------- UNDEFINED
}
render() {
return (
<Provider store={store}>
<PersistGate
loading={<ActivityIndicator />}
persistor={persistor}>
<View style={styles.fullScreen}>
<StackView />
<Tutorial navigation={this.props.navigation} /> // <---------- this.props.navigation is UNDEFINED
</View>
</PersistGate>
</Provider>
);
}
}

navigation prop is only available on components that are in navigation stack or child components that are using withNavigation HOC.

Your App class is not included in the Navigator stack, due to which, the navigator class cannot provide any navigation props to it, that is why you are getting an undefined error because the navigation object doesn't exist.
Also if you need to open <Tutorial/> as a first screen, include it as the first element in the StackNavigator. That ways, it'll be always called first.
PS: You can additionally provide navigation={this.props.navigation} element to different components, which does not have to be included in the navigator class. For example, you need to open a dropdown element, which provides a sign out option that navigates to a home screen. In this use case, it'll be better to provide the navigation props as you have provided it in the question. But care needs to be taken, that the class providing the prop should have the object present
EDIT
According to your requirement, you need to display a tutorial for each screen that you open. You can do it via two approaches.
Your way - You can declare it in the stack navigator. But your <Tutorial/> component will contain multiple conditions to check for the route and display the appropriate data ( that's what I can think of right now ).
You can declare a common component that only shows the message in a modal. You can then declare an optional prop for each component, and in each of your components, you can check if this optional prop exists, you can show your common display <Tutorial/> component.
This is what I can think of currently. Will update if I have more thoughts :)
Hope this answers.

Related

How to modify DrawerNavigatorItems items

In the docs from DrawerNavigatorItems it have the following property:
items - the array of routes, can be modified or overridden
It says you can modify the array.
How can I do that?
It doesn't explain what is an "array of routes". It's just strings? An object?
An example of what I want to do is get the full array and add an extra one at the end, something like
items: [...items, someNewItem]
One solution is to define our customized Component. Furthermore, we could utilize Redux to manage the state.
Excerpt from an APP which I created:
//The navigation part
const MainDrawer=createDrawerNavigator({
MainNavigation,
K:KChartViewScreen
},{
contentComponent:CoinDrawer,
edgeWidth:-1 //to disable gesture from bound open the drawer in unexpected screen. but you could do some work to enable it and without problem, I just handle in an easy way.
});
The following is the CoinDrawer:
class CoinDrawer extends React.Component{
renderMarkets(){
...
}
render(){
return (
<View>
<ScrollView>
<SafeAreaView style={styles.container} forceInset={{ top: 'always', horizontal: 'never' }}>
{this.renderMarkets()}
</SafeAreaView>
</ScrollView>
</View>
)
}
function mapStateToProps(state){
return {
config:state.config,
crypto:state.crypto,
trade:state.trade
}
}
export default connect(mapStateToProps,{SetCurrentMarket})(CoinDrawer);
Hope this can help you.
You can pass your drawer navigation items in your navigation file. You can create one like below
import { createDrawerNavigator } from 'react-navigation-drawer'
const MainNavigator = createDrawerNavigator(
{
Main,
Landing,
},
{
contentComponent: Drawer,
},
)
I hope you understand we can make different types of navigators like switch, stack, drawer, etc.
After this, you can call your drawer-navigator inside AppNavigator like below
const AppNavigator = createStackNavigator(
{
MainNavigator,
Account,
Success,
},
{
initialRouteName: 'MainNavigator',
headerMode: 'none',
},
)
Now whatever route you will insert in MainNavigator object, it would be shown into the drawer.
There may be several ways of using navigation service, I used this one.
Hope this helps.

Show a tab bar item or not based on Redux state in React Native

I've got a React Native app with React Navigation and I need to show or hide a tab based on Redux state. I'm using createBottomTabNavigator and have written this:
function createTabNavigator(props:TabNavigatorProps){
debugger;
const isClient = WHAT_TO_PUT_HERE??
const tabs = isClient
? {
//something
}
: {
//something else
}
return createBottomTabNavigator(
tabs,
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ... ,
tabBarLabel: ...,
}
tabBarComponent: (props) => customTabBar(props, isClient),
tabBarOptions: {
...
},
});
};
My createTabNavigator works the way as intended if I manually set isClient to true or false, though I need this from redux state.
I've also tried connecting it, and mapStateToProps get called with the correct state, however it doesn't call createTabNavigator again so state changes aren't updated to my view hierarchy.
How can I add/remove a tab (and actually re-render altogether as that tab also involves some custom styling/rendering of the whole bar) based on Redux state?
react-navigation does not provide any API to hide a tab from BottomTabNavigator.
However, it allows providing custom TabBarComponent. It also exposes BottomTabBar component which is the default TabBarComponent for BottomTabNavigator.
This BottomTabBar component takes a property getButtonComponent to get the component for rendering each tab. It is provided the tab details in argument and is expected to return component to be used for rendering that tab.
To achieve hiding of tabs dynamically, you provide your own function for getButtonComponent. Check the details of tab with your state to decide whether it should be visible or hidden. Return an 'empty component' if you want to hide it, else simply call the default.
/**** Custom TabBarComponent ****/
const BlackHole = (props) => []
let _TabBarComponent = (props) => {
const getButtonComponent = (arg) => {
const hide = props.hiddenTabs[arg.route.key];
if (hide) {
return BlackHole;
}
return props.getButtonComponent(arg)
}
return (<BottomTabBar {...props} getButtonComponent={getButtonComponent}/>);
};
const mapStateToProps = (state) => ({
hiddenTabs: state.hiddenTabs
});
const TabBarComponent = connect(mapStateToProps)(_TabBarComponent);
/**** Navigation ****/
const TabNavigator = createBottomTabNavigator({
Tab1: createStackNavigator({Screen1}),
Tab2: createStackNavigator({Screen2}),
Tab3: createStackNavigator({Screen3}),
}, {
tabBarComponent: props => (
<TabBarComponent {...props} style={{borderTopColor: '#605F60'}}/>
)
});
See this snack for POC: https://snack.expo.io/BJ80uiJUH
We had the same requirement but in react navigation we cannot have dynamic tab, as we have to define all the routes statically well in advance.
Check this answer, in which I have explained in detail about how we achieved it.
Also have mentioned other possible approach (which we have not done).
There is one more interesting article you might want to check on this.
Make sure you are not calling redux state inside a HOC Component ,
For state management ,you can try to render it inside a component , try this logic :
class TabIndex extends Component {
render() {
// coming from redux
let isClient= this.props.isClient;
return (<View>
{isClient?<TabIndexNavigator ifYouNeed={ isClient } />
:
null}
</View>)
}
}
module.exports = TabIndex
then import it normally in your main stackNavigator
const StackMain = StackNavigator(
{
TabIndex: {
screen: TabIndex
},
...
}

AWS Amplify Auth / react-navigation: How to access auth state?

In my React Native mobile application, I would like to authenticate my users using AWS Amplify Authentication and manage navigation between screens using React Navigation. I tried to implement everything "by the book", i.e. as per the docs and my App conceptually looks like this:
// import statements skipped
class FirstTabScreen extends React.Component {
render = () => {
{/* access some data passed in screenProps */}
const someData = this.props.screenProps.someData;
{/* TODO: how to access AWS Amplify auth data here ?!? */}
{/* this.props contains screenProps and navigation objects */}
console.log("this.props: ", this.props);
{/* this.props.authState is undefined */}
console.log("this.props.authState: ", this.props.authState);
return <View><Text>First Tab - some data: {someData}</Text></View>
};
}
class SecondTabScreen extends React.Component {
render = () => {
const otherData = this.props.screenProps.otherData;
return <View><Text>Second Tab - other data: {otherData}</Text></View>
};
}
const AppNavigator = createBottomTabNavigator({
FirstTab: { screen: FirstTabScreen },
SecondTab: { screen: SecondTabScreen },
});
const AppContainer = createAppContainer(AppNavigator);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
someData: null,
otherData: null,
};
}
componentDidMount = () => {
this.setState({ someData: 'some data', otherData: 'other data' });
};
render = () => {
return (
<Authenticator>
<AppContainer
screenProps={{
someData: this.state.someData,
otherData: this.state.otherData,
}}
/>
</Authenticator>
);
};
}
export default App;
The code sample above skips a few important details; I have created a full Expo Snack.
The AWS Amplify Authenticator wraps the application and makes authentication data available to embedded components as e.g. this.props.authState; see docs.
My problem now is that I don't understand how to access this auth data in e.g. FirstTabScreen: In that component, this.props contains screenProps and navigation objects, but this.props.authState is undefined. With AppContainer and AppNavigator in between Authenticator and e.g. FirstTabScreen, I don't see how to pass auth data as props or transport them by some other means.
Furthermore, I just pass all data to all embedded components in screenProps; this does work, but not all screens require all data. How would I go about passing them only the data they actually require ? I know about passing parameters to routes, but all the code samples in there have a Button and use this.props.navigation.navigate(...) in the onPress handler. I don't have any buttons on those screens and don't need any - after all, navigating to a different tab is what the tabs in the bottom tab nav bar are for ?!?
Could anyone kindly shed some light on this for me ?
using screeProp is not encouraged on react navigation v3
I believe it's no longer encouraged to put navigation such as TabNavigator nested within a component. I'm not quite sure how to handle such cases gracefully in V3. I wanted to upgrade from V1 to V3 recently and gave up on the upgrade since I couldn't find enough info on what to do with the nested navigators I have.
another idea from esipavicius
do not wrap screens to stack navigator which include the tab navigator. Maybe you Can use navigationAction setState or setParams with params and navigator key. When You Can dispatch it. And automaticaly changes state with params to which scren you dispatched.
OPTION 1 - USING SCREEN PROP
Your option of passing screen props
class App extends React.Component {
componentDidMount = () => {
this.setState({ someData: 'some data', otherData: 'other data' });
};
render = () => {
return (
<Authenticator>
<AppContainer
screenProps={{ myProp: "test" }}
/>
</Authenticator>
);
};
}
OPTION 2 - USING THE REACT CONTEXT API
Using the Context api and wrapping it around your <AppContainer>.
class App extends React.Component {
render = () => {
return (
<FormContext.Provider value={this.state}>
<AppContainer />
</FormContext.Provider>
);
};
}
Then consume it in each one of your screens.
class FirstTabScreen extends React.Component {
render(){
return(
<FormContext.Consumer>
{ (context) => (
// your logic
)}
</FormContext.Consumer>
};
}
Option 3 - TAB NAVIGATOR OBJECT
You can pass a TabNavigatorObject
createBottomTabNavigator(RouteConfigs, BottomTabNavigatorConfig);
You can pass an extra option to the BottomTabNavigator in the BottomTabNavigatorConfig.
const BottomTabNavigator = StackNavigator(routesConfig, BottomTabNavigatorConfig)
The BottomTabNavigatorConfig is an option and you can check the api docs
{ initialRouteName: 'Home', navigationOptions: .... }
Option 4 - RETRIEVE PARAM FROM PARENT
as explained on github and in the api docs, you can retrieve the parent component and then use getParam.
const parent = navigation.dangerouslyGetParent();
const yourParam = parent.getParam('yourParamName', 'default value')
You can find many discussion about passing props with tab navigator on github
1) I would suggest to not use any logic like this outside navigator. If you want login, do something like:
const rootStack = createSwitchNavigator({
Login: { screen: LoginNavigator },
Main: createBottomTabNavigator({
FirstTab: { screen: FirstTabScreen },
SecondTab: { screen: SecondTabScreen },
});
})
It will give you more login possibilities and once you know user is loggedIn just navigate him to Main stack.
2) Use any store for app. For example react-navigation works great with redux. You can save all info there.
Try to not use screenProps for simple cases like this. Also you don't need to send authState to other screens, just save to store.
3) Once you want logout. Do: Auth.signOut(), clean store, and navigate to Login screen.
Try to follow these simple steps and you will be able to implement much more complicated things

React Navigation - goBack() is automatically invoked for stack navigator screen

I have 3 stack navigator screens (Home, Item List, Item Detail -> same order) inside drawer navigator and all three screens are hooked up to redux store with connect() helper.
When I do navigate('ItemDetail') from ItemList, it navigates to the screen and immediately comes back to ItemList screen. I am not sure why.
Following is my navigation structure -
const AppStack = createStackNavigator(
{
Home: {
screen: HomeScreen
},
ItemList: {
screen: ItemListScreen
},
ItemDetail: {
screen: ItemDetailScreen
}
},
{
initialRouteName: 'Home'
}
);
const DrawerNav = createDrawerNavigator(
{
DrawerApp: AppStack
},
{
drawerPosition: 'right'
}
);
const SwitchStack = createSwitchNavigator(
{
Loading: Loading,
Auth: AuthStack,
App: DrawerNav
},
{
initialRouteName: 'Loading'
}
);
This is how my each navigation screen component looks -
export class ProviderListScreen extends Component {
render() {
const { navigation } = this.props;
// ItemList is hooked up to Redux via connect() helper
return <ItemList navigation={navigation} />;
}
On my ItemDetail component, I get the Item data through route params from ItemList screen and I also dispatch an action (To reset some part of the store state) in component did mount. As soon as I do that, previous screen (ItemList) is automatically rendered.
Inside item detail, I make API call to create booking for that item and the booking object is managed by redux. Once I land on the ItemDetail, I reset the booking object for new booking data.
Here is the snippet of ItemDetail's componentDidMount -
componentDidMount() {
this.props.resetItembooking();
}
I am not sure what is causing this behaviour. If I remove the ItemList screen and jump directly to ItemDetail screen from HomeScreen, this issue does not occur.
Any help is appreciated.
I had the exact same problem however I tried the answer given in the original post comments sections given by Awadhoot but this did not work for me.
For anyone still trying to solve this issue, ensure you do not have any recurring intervals setup. Therefore you should always clear intervals before navigating away:
clearInterval(this.intervalId);
In react-navigation 5 you can use the useIsFocused hook for that:
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}
From the docs: https://reactnavigation.org/docs/use-is-focused/

onPress event in navigationOptions property

I'm trying to create a menu button in the header area of one of my screens.
However, I can't figure out how to get the onPress navigation to work.
Obviously, the code below will produce an error because navigate is defined after it's first used.
But, when I try to move the const { navigate } declaration up before I set the navigationOptions property, I get an "unexpected token" error in my IDE (visual studio code).
Is there any way around this?
Thanks!
export default class HomeScreen extends React.Component
{
constructor(props) {
super(props);
}
static navigationOptions = {
title: 'Seekers',
headerRight:
<TouchableHighlight onPress={ () => navigate('Menu', {
menu: options
})}>
<Image source={require('./images/menu.png')} style={{width: 50, height: 50}} />
</TouchableHighlight>,
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
... more view stuff...
Customizing the header changed in the last few updates of React Native and passing navigation options changed too. According to the doc Screen Navigation Options, Dynamic configuration is possible and the 'navigationOptions' now can be used as a function which takes navigation Options as parameters.
For example, I wanted a button to close and open my DrawerNavigator in the header. In my router.jsfile, I wrote:
export const Root = StackNavigator({
App: {
screen: AppDrawer,
navigationOptions: ({navigation}) => ({
headerLeft: <BurgerMenu nav = {navigation} />,
})
}
}, {
headerMode: 'screen',
});
In your case, I think you should declare your navigationOptions as a function:
static navigationOptions: ({navigation}) => ({
title: 'Seekers',
headerRight: <TouchableHighlight onPress={ () =>
navigation.navigate('Menu', { menu: options })}>
<Image source={require('./images/menu.png')} style={{width: 50, height: 50}} />
</TouchableHighlight>,
};
I didn't personnally try to set navigationOptions from the component itself. Instead I declare the options in my StackNavigator.
Let me know if it works !
EDIT
How does my navigation work ?
For the navigation side of my application I'm simply using react-navigation.
It would be pretty long to copy all the content of my files here so I created a Gist for you to see how I implemented that.
First, I create a router.js file, in which I define my screens and the type of navigation (here). Focus on the variable named Root, this is my main StackNavigator. This is the entry point of my app.
Second, I edited the index.android.js and index.ios.js so they both render the same component.
import { AppRegistry } from 'react-native';
import App from './src/index';
AppRegistry.registerComponent('obexto_expensemanager', () => App);
Third. What does my App component contains ? This component is the one I use to render my entry point defined in the router.js. What is important in this file is:
import { Root } from './config/router';
class App extends Component {
render() {
return (
<Root />
)
}
}
I use the StackNavigator defined in router.js as my main component for my app.
Why ? I like the way this navigation works because you separate clearly your stacks from your components. And all your navigation is set in a single file. So, in your project you'll have your scene folder with all your components and a router where you connect them.
In my example I wanted to add a button in my header so I could open my DrawerNavigator. The header can be accessed from the navigationOptions when you define your Navigator, that's why I defined the option headerLeft in my App StackNavigator, so it's visible in every screen.
I don't know if this is clear for you but don't hesitate to tell me if it's not and what is still obscur to you. I'll do my best to help !
Just a thought, how about declaring the navigationOptions directly on the StackNavigator (assuming that's what you are using). For example
`
const Nav = StackNavigator({yourRoutes}, {
{
navigationOptions: {...}
}
});
`