Different Headers in StackNavigator - android

I have implemented a StackNavigator in which I removed the header for styles purpose.
Now I have a Component inside the StackNavigator for which I'd like to get the header back. How do you reckon I do it ?
Here is my StackNavigator :
const Nav = StackNavigator(
{
SplashScreen: {screen: SplashScreen},
Login: {screen: Login},
Register: {screen: Register},
Main: {screen: MainNav},
},
{
headerMode: 'none',
});
But for the Register screen, I would like to have a header (to let the user know that he can go back).
I tried doing this :
static navigationOptions = {
title: 'Register',
header: {
}
}
But I don't quite know what to put inside the header part.

Personally what i did: I had set the height of the header to 0 when i didnt want it to show and to n to show it so i could do somenthing like
height: condition ? 0 : 10
otherwise someone answered here
You can do it programmatically with the following code:
static navigationOptions = ({ navigation }) => ({ header:
> (navigation.state.params.thanks) => <HeaderView
> content={navigation.state.params.thanks} /> : null, })
And then you
can set the state params with this:
componentWillMount() { this.props.navigation.setParams({ thanks:"Something" }); }
Although I haven't tried the code myself and I'm not
sure if something like the HeaderView is accessible for the public api
in react-navigation and if there is a content property on that. But I
think in an abstract way, this is how you set it programmatically.
I didn't try it either but it might work
Edit: the answer given in github is definitely better, you can import HeaderView from react-navigation and do header: condition ? HeaderView : null to show and hide it

Related

How to navigate between a few AppContainers

how can I navigate between AppContainers?
I'm detecting a correct stack in App.js
const stack = User.isAuthorized() ? authStack : unauthStack;
After user enter the login and password, he need switch the stack from unauthStack.SignIn to authStack.List.
const unauthStack = createAppContainer(createStackNavigator({
SignIn: { screen: SignIn },
ForgotPassword: { screen: ForgotPassword },
}));
const authStack = createAppContainer(createBottomTabNavigator({
List: { screen: GeneralStack },
Add: { screen: NewEventStack },
}));
I've tried to Google it but can't find any working examples. And saw some information that complete reload the app could be a reason of memory leak or something like that...
So what's the correct way to do this?
You shouldnt approach multiple containers, rather make 2 stacks and adda switchNavigator and if its logged in then display accordingly. See example below:
const navigation = createAppContainer(
createSwitchNavigator(
{
App: HomeStack, // these are after login pages
Auth: AuthStack,// these are before login pages
},
{
initialRouteName: isToken?'App':'Auth', //checking if token exists
},
),
);
Please check the code and if any doubts do ask.
Hope it helps

React Navigation - wrapping header and tab navigator in Blurview looses props

I am using React Navigation 2 for a simple RN project with Expo. I am trying to get the header and tabs on the bottom to display over a blurred background so I have done a HOC to wrap the library Header with a BlurView to provide that functionality. It renders the blur fine but unfortunately the title, back buttons etc. are lost in the process. Is there a way to do that in React Navigation, the code I use is as follows:
const wrappedHeader = props => (
<BlurView tint="light" intensity={80} style={styles.header}>
<Header {...props}/>
</BlurView>
);
class HomeScreen extends React.Component {
static navigationOptions = {
header: props => wrappedHeader(props),
headerTitle: "Home Screen",
};
....
}
This is a tricky question that truly got me thinking for awhile.
Here's the solution I've found to get a native iOS feeling for a tab bar navigator:
import React from 'react';
import { StyleSheet } from 'react-native';
import { BlurView } from 'expo';
import { BottomTabBar } from 'react-navigation-tabs';
const styles = StyleSheet.create({
blurView: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
},
bottomTabBar: {
backgroundColor: 'transparent',
},
});
export default function TabBar(props) {
return (
<BlurView tint="light" intensity={90} style={styles.blurView}>
<BottomTabBar {...props} style={styles.bottomTabBar} />
</BlurView>
);
}
The problem seemed to be related to the BlurView styling.
Note: this code will only work after setting the tabBarComponent option on your navigator as the following:
export default createBottomTabNavigator({
// This part should be different on your side
Feed: FeedScreen,
Me: MeScreen,
Settings: SettingsScreen,
}, {
tabBarComponent: TabBar,
});
For the header, I guess it must be the same trick, but you would need to replace bottom: 0 with top: 0.

React Native Navigation

I am making an app , in which I used Drawer Navigator and i nested a Stack Navigator inside it . Drawer contains screen HOME , PRODUCTS , Profile etc.
While i used Stack navigator inside Home screen to switch to 3 different screen
Products->ItemDescription .
Issue 1: If I go to Product or Item Description via Home .And if i open drawer in products or ItemDescription , I cant go back to home on clicking HOME from drawer .WHile clicking other options on drawer menu i can switch to diff screens.
Issue 2 : I have a Navbar from 'navbar-native' , which i used in every component namely HOME , PRODUCTS , ItemDescription , Cart etc. Can you help me to link with redux so that i can open different screen on clicking on its ICON namely CART and SEARCH screen.
Drawer Code :
const RootDrawer = DrawerNavigator({
Home: {
screen: HomeScreen,
style :{backgroundColor:'blue'},
},
Profile: {
screen: ProfileScreen,
},
Products: {
screen: Products,
},
Cart: {
screen: Cart,
},
});
export default RootDrawer;
HomeScreenCode :
class Home extends React.Component {
constructor(props){
super(props) ;
this.openUp = this.openUp.bind(this)
}
openUp(){
this.props.navigation.navigate('DrawerOpen');
// open drawer passed to all components to open Drawer from hamburger
//iconpresent at NAVbar
}
render() {
return (
<SimpleApp key={1} screenProps={this.openUp} />
);
}
}
const SimpleApp = StackNavigator(
{
drwlyout: { screen: DrawerLayoutMain , navigationOptions:({navigation}) => ({ header: false } ) },
prodlyout: { screen: Products , navigationOptions:({navigation}) => ({ header: false })},
itemdsclyout: { screen: ItemDescription , navigationOptions:({navigation}) => ({ header: false })},
navbarlayout: { screen: NavBarComponent , navigationOptions:({navigation}) => ({ header: false })},
} );
function mapDispatchtoProps(dispatch){
return bindActionCreators({getDataFirebase:getDataFirebase},dispatch);
}
export default connect(null,mapDispatchtoProps)(Home);
For your first issue, you will need a navigation reducer, a basic description of how to implement Redux with React-Navigation can be found here
When you have to listen for the specific navigation action inside of your reducer. For example, your DrawerNavigator looks something like this:
const nav = (state = INITIAL_NAV_STATE, action) => {
let nextState;
switch (action.type) {
case REHYDRATE:
// if you're using redux-persist
case 'Main': // where 'Main' is your StackNavigator
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Home' }),
state
);
break;
See the 'routeName' which is the name of your Home screen. So, your root or Main drawer Navigator should look something like this:
const routeConfig = {
Main: {
screen: MainStackNavigator,
navigationOptions: {
drawerIcon: ({ tintColor }) => (
<Icon name="a006_start" size={28} color={tintColor} />
),
drawerLockMode: 'locked-closed'
}
},
.... additional Code ....
.... like navigationOptions etc. ....
const MainDrawerNavigator = DrawerNavigator(routeConfig, routeOptions);
export default MainDrawerNavigator;
I'm sorry that I haven't worked with 'navbar-native' yet but I guess that if you're wiring up your redux configuration like this you could listen to the particular navigation actions. Which then can be processed inside of your navigation reducer.

How to finish current component while navigating to next component in react native

Hi I am trying to navigate to next component using navigate function. I am using react-navigation for the navigation among multiple components.
Suppose I have index.android.js and DashboardScreen.js component. I am trying to navigate to DashboardScreen.js component from index component.
It is navigating but index component always retain in component stack. when I press back then it opens index.android.js which should not be. Does anyone know how to manage this in react-native. In Android, finish() works for this.
navigate("DashboardScreen");
When I am navigating from SplashScreen to EnableNotification then SplashScreen should be destroyed, if I am navigating from EnableNotification to CreateMessage then EnableNotification should be destroyed and if I am navigating from CreateMessage to DashboardScreen then CreateMessage should be destroyed. As of now no component is being destroyed.
index.android.js
class SplashScreen extends Component {
render() {
if (__DEV__) {
console.disableYellowBox = true;
}
const { navigate } = this.props.navigation;
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
if (value) {
navigate("DashboardScreen");
}
});
return (
....
);
}
}
const App = StackNavigator(
{
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
},
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
DashboardScreen: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "Splash"
}
);
Just use 'replace' in place of 'navigate'
this.props.navigation.replace('Your Next Component Name')
First of all, using AsyncStorage in an a synchronous function (most especially a lifecycle one) is such a bad idea. You should typically keep ASyncStorage to places in your folder / app structure that make sense for where you access/keep data but since that's not the question I will just mention it quickly here...
Basically you are asking to navigate once the ASync method completes itself based on EVERY render... Those new to RN should know that an awful lot of things can cause a render to fire. Some cases, the render function can fire (I have seen this many times before) 10 or more times before finalizing the last render. This means you would have fired that ASyncStorage method 10 times... definitely something to think about when implementing this stuff. So more or less, the .then(); part of the AsyncStorage function is firing long after the render has already finished doing it's thing. If it was a reasonable approach to use I would say to put the return part of the render function inside of the .then((value) => { return ( ... ); });. But this is an even worse idea. Basically you need the right lifecycle method here and it's NOT the render method.
Anyway, since I have never used this component library before I can only help nudge you in the right direction so here goes... These docs on their webpage seem to say that you need a reference to the props navigator passed down to the component in which you are using it. So if you created the navigator in this class, you would use this.refs.whateverYouNamedTheNavigatorReference.navigate('SomeItemName'). If you are in the class that has been passed this navigator as a prop, you use this.props.passNavigatorPropName.navigate('SomeItemName'). I see you are using variable deconstruction to get the navigate callback but I would caution on doing this, this way because I have seen it cause errors by grabbing an old version of the navigate function or its parent reference by accident and causing a cascading error effect.
Also, if you are going to be using ASyncStorage in a component file (again, would recommend putting this in a component/class where your data is accessed throughout the app...) and you are going to use it to decide the app should navigate forwards/backwards... definitely remove it from the render function and put it in maybe the constructor, componentWillReceiveProps, componentDidReceiveProps or componentWillUpdate lifecycle functions. That way it fires based on an update, a new passed prop obj or one time as the component is built. Anything is better than firing it every single render.
Lastly, I do not know what you have setup for your StackNavigator route stack object but you would need to have the keyword you used "DashboardScreen" in there pointing to an actual component that has been imported properly. The "DashboardScreen" keyword most likely would connect in your StackNavigator object to some component import like so...
import Dashboard from '../Views/DashboardScreenView';
StackNavigator({
DashboardScreen: {
screen: Dashboard,
path: 'dashboard/:main',
navigationOptions: null,
},
});
There is a simple way here: use "replace" (reference link repleace in navigation ,For example, you are at the screen "Login" ,
and you want to move to screen "Home", insert this code in screen "Login"
<TouchableOpacity onPress={() => { this.login() }}>
<Text}>Click me to Login</Text>
</TouchableOpacity>
and method login:
login(){
this.props.navigation.replace('Home')
}
Screen "Login" will be replaced by "Home", in Android, press Back Button =>app exit, no back screen "Login"
Based on your requirement, i suggest following setup:
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
}
});
AppNavigator.js
const AppNavigator = StackNavigator(
{
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
Dashboard: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "EnableNotification"
}
);
In your index.android.js, you will render the SplashNavigator.
The SplashNavigator will render the SplashScreen. It has initial state value isReady set to false, so it will render a loading text until the #ProductTour:key value from AsyncStorage is loaded (AsyncStorage is async function, u should not put it in your render function). It will then render your AppNavigator and render your EnableNotification as initial route.
class SplashScreen extends Component {
constructor() {
super(props);
this.state = {
isReady: false,
}
}
componentDidMount() {
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
// you will need to handle case when `#ProductTour:key` is not exists
this.setState({
isReady: true,
});
});
}
render() {
const { isReady } = this.state;
return (
<View style={{flex: 1}}>
{
isReady ?
<AppNavigator />
: <Text>Loading</Text>
}
</View>
);
}
}
Then on EnableNotificationScreen and CreateMessageScreen, change your navigate route function to use NavigationActions.reset from doc
Example:
import { NavigationActions } from 'react-navigation';
handleOnPressButton = () => {
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: "CreateMessage" })
]
});
this.props.navigation.dispatch(resetAction);
}
Yes in react native you can finish the current screen before navigating to new screen with the help of NavigationActions . Please refer this link -
http://androidseekho.com/others/reactnative/finish-current-screen-on-navigating-another-in-react-native/
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: null}
}
}
});
Import StackActions and NavigationActions from react-navigation.
import { StackActions, NavigationActions } from 'react-navigation';
below code for performing Action
navigateToHomeScreen = () => {
const navigateAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: "HomeScreen" })],
});
this.props.navigation.dispatch(navigateAction);
}

Back key react native android

I am trying to go back to previous view in my react native app using this code
'use strict';
var React = require('react-native');
var {
AppRegistry,
Component,
StyleSheet,
Text,
View,
BackAndroid,
Navigator
} = React;
var HomePage = require('./HomePage');
class DetailsPage extends Component{
constructor(props){
super(props);
}
render(){
return(
<View style={styles.container}>
<Text style={styles.text}>
{this.props.description}
</Text>
</View>
)
}
}
BackAndroid.addEventListener('hardwareBackPress', function() {
this.props.navigator.pop(); // line 32
return true;
});
var styles = StyleSheet.create({
container: {
flex: 1
},
text:{
color:'#000',
textAlign:'center',
fontWeight:'bold',
flex:1,
fontSize:20
},
});
module.exports = DetailsPage;
While debugging I see that the back event is detected successfully but it crashes at this line this.props.navigator.pop() giving me this error.
Cannot read property 'props' of undefinedhandleException #
D:\React\Kora\node_modules\react-native\Libraries\JavaScriptAppEngine\Initialization\ExceptionsMana…:61handleError
#
D:\React\Kora\node_modules\react-native\Libraries\JavaScriptAppEngine\Initialization\InitializeJava…:80ErrorUtils.reportFatalError
#
D:\React\Kora\node_modules\react-native\packager\react-packager\src\Resolver\polyfills\error-guard.…:28guard
#
D:\React\Kora\node_modules\react-native\Libraries\Utilities\MessageQueue.js:43callFunctionReturnFlushedQueue
#
D:\React\Kora\node_modules\react-native\Libraries\Utilities\MessageQueue.js:86onmessage
# debuggerWorker.js:39
my guess it that this.props can not be accessed out side the class braces but I don't know how to overcome this problem. if I put the BackAndroid.addEventListener inside the class it gives me error
UnExpectedToken
edit: BackAndroid is now deprecated. You should probably be using BackHandler instead.
In your event, this does not refer to what you think it refers to.
In this case, it refers to the object on which the event is called.
The way I did it in my react-native app, was by adding the event in the componentDidMount()-function of my main component (the component in which the Navigator is rendered).
I added it there the (sort of) following way:
class ViewComponent extends Component {
...
componentDidMount() {
//the '.bind(this)' makes sure 'this' refers to 'ViewComponent'
BackAndroid.addEventListener('hardwareBackPress', function() {
this.props.navigator.pop();
return true;
}.bind(this));
}
...
}
It should work like this in your project.
The componentDidMount() is fired right after the initial render. You could probably also use componentWillMount(). So it adds the event right after the first time it renders. It only gets fired off once, so there are no overlapping events and stuff like that.
I wouldn't, however, add the event listener on scenes. It brings the risk of perhaps adding the event twice. Although I'm not sure if adding that event twice would actually change anything.

Categories

Resources