Navigate if my var is not set in my redux state - javascript

I'm making somme connexion/inscription screens. I have a profil bottom tab button. The goal is if the user click on it, It will either show his profil page if he is connected (I'm setting an user in my redux store), or show a Landing screen with the login button and subscribe button if he is not.
How can i navigate to this landing page every time the user click on "Profil" on my bottom tab bar if the redux store is empty ?
Here is my profil code :
// Screens / Profil.js
import React from "react";
import { StyleSheet, View, Text, Button } from "react-native";
import { color } from "../constants/color";
import { connect } from "react-redux";
/*
* Class Profil
* Appears when the users click on the profil button in the bottom tab bar
* Return JSX component
*/
class Profil extends React.Component {
constructor(props) {
super(props);
}
// This is what I have tried so far
componentWillMount() {
if (this.props.currentUser == null) {
this.props.navigation.navigate("Landing");
}
}
/*
* Render the page
* #param null
* #return JSX component
*/
render() {
const { currentUser } = this.props;
console.log(currentUser);
if (currentUser == null) {
return (
<View style={styles.mainContainer}>
<Text>You are not connected</Text>
</View>
);
} else {
return (
<View style={styles.mainContainer}>
<Text>Welcome {currentUser.user.username}</Text>
<Button title="Déconnexion" onPress={this._disconnection} />
</View>
);
}
return null;
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
backgroundColor: color.whisper
}
});
const mapStateToProps = state => {
return {
currentUser: state.connection.currentUser
};
};
export default connect(mapStateToProps)(Profil);
So when I first click on my Profil it goes well on my landing page:
But if I click again on Profil, It does not show my landing page :
I guess it's because the component did not rerender himself . How could i Handle a constant redirection according to my redux state var ?
UPDATE : My stack Navigator
const ProfilStackNavigator = createStackNavigator({
Profil: {
screen: Profil,
navigationOptions: {
title: "Profil"
}
},
Landing: {
screen: Landing,
headerMode: "none",
navigationOptions: {
header: null
}
},
SignUp: {
screen: SignUp,
navigationOptions: {
title: "Inscription"
}
},
SignIn: {
screen: SignIn,
navigationOptions: {
title: "Connection"
}
}
});

I think you have two options to solve this, the first one is using NavigationEvents and the second one is navigationOptions
First option, add didFocus listener to your profile component, which will get fired when profile tab is focused.
class Profil extends React.Component {
subscribe = null;
constructor(props){}
focused = () => {
if (this.props.currentUser == null) {
this.props.navigation.navigate('Landing');
}
};
componentDidMount = () => {
this.subscribe = this.props.navigation.addListener('didFocus', this.focused);
};
componentWillUnmount() {
this.subscribe && this.subscribe.remove();
this.subscribe = null;
}
render() {
...
}
}
Second option, with navigationOptions add tabBarOnPress which gets fired when you click the tabbar.
Profil: {
screen: ProfilStackNavigator,
navigationOptions: {
tabBarLabel: 'Profil',
tabBarOnPress: ({ navigation, defaultHandler }) => {
// console.log('Pressed');
defaultHandler();
},
},
},

I have successfully done this.
You have the correct idea.
Perhaps your navigation object is the issue?
Here is an example I coded for a 3D Earthquake Visualizer with React/Redux/Three. I passed the history object as a prop to my components and used that to dynamically change the routing. (not included in example)
I would suggest ensuring your router API is correct and then logging the state on will mount lifecycle.
I hope this has been helpful. Good Luck! :)
const mapStateToProps = state => {
return {
...
quakes: state.viz.quakes,
...
};
};
componentDidMount(){
// if data hasn't download redirect to splash
if (this.props.quakes[this.props.feedIndex].length === 0) {
this.props.history.push("/");
}
...
}

Related

How to get access to navigate/goBack function in the class that rendered AppContainer in ReactNative

I've created an app with several pages to navigate through and it works fine when I send navigation requests from child pages (goBack or navigate('...')). The problem is that I need to switch pages from a class that created the AppContainer. It looks more complicated. I tried in several ways with refs, but all the time navigate function is not accessible. I want to push a home page in my callback function of the BasicFooter.
I tried to use dispatch as well, but the same problem. I am not sure if it is even possible to make it work by reference to AppContainer or do I need to build a structure with
<NavigationContainer>
<Stack.Navigator>{/* ... */}</Stack.Navigator>
</NavigationContainer>
That's my code:
export default class App extends React.Component {
constructor(props) {
super(props);
this.refNavi = React.createRef();
this.goToMain_audio = this.goToMain_audio.bind(this);
}
goToMain_audio() {
console.log("Object ", this.refNavi.current.navigation.navigate('Home')); //<==Undefined!
}
render() {
return ( <
View style = {
styles.container
} >
<AppContainer ref = {
this.refNavi
}
style = {
styles.container
} </AppContainer>
<View style = {
styles.footer1
}>
<BasicFooter style = {
styles.footer1
}
clickedAudio = {
this.goToMain_audio
}
clickedMood = {
this.refNavi.current.navigate('Home')
} //<== Undefined!
>
<
/BasicFooter> </View>
}
}
const AppNavigator = createStackNavigator({
Home: {
screen: MainScreen,
},
SettingsPage: {
screen: Settings,
},
SettingsMain: {
screen: SettingsMain,
},
ConnectionPage: {
screen: Connection,
}
}, {
initialRouteName: "Home",
transitionConfig,
header: null,
headerMode: 'none'
});
const AppContainer = createAppContainer(AppNavigator);

React Native activeTintColor not getting applied on selected drawer item

React Native activeTintColor not getting applied on selected drawer item. My react native navigation routes looks like,
-> DrawerNavigator
-> StackNavigator
-> HomeScreen
-> FirstScreen
-> SecondScreen
-> ThirdScreen
routes.js
const RootStack = createStackNavigator(
{
Home: { screen: HomeScreen },
ChapterGroup: { screen: ChapterGroupScreen },
Chapter: { screen: ChapterScreen },
}
const DrawerStack = createDrawerNavigator(
{
Home: {
screen: RootStack,
params: { id: 1 }
},
Kural: { screen: ChapterGroupScreen, params: { id: 2 } },
Detail: { screen: ChapterGroupScreen, params: { id: 3 } }
}, { contentComponent: DrawerComponent}
}
export default DrawerStack;
I managed to display the First, Second, thirdScreens on the sidebar by creating a new DrawerComponent which will navigate to the appropriate stack screen on drawer item click.
DrawerComponent.js
resetStack = route => {
let pressedDrwaerItem = route.route.key;
let id = route.route.params.id;
this.props.navigation.dispatch(
StackActions.reset({
index: 1,
actions: [
NavigationActions.navigate({
routeName: "Home"
}),
NavigationActions.navigate({
routeName: "ChapterGroup",
params: { title: pressedDrwaerItem, no: id }
})
]
})
);
}
render() {
return (<ScrollView>
<DrawerItems
{...this.props}
onItemPress={this.resetStack}
</DrawerItems</ScrollView>)
}
It properly gets navigated to the ChapterGroup Screen on Home stack but the drawer activeitem points to the Home not the second or third custom name. I think it may be because all the other screen exists inside the Rootstack. Is there anyway to manually set the second drawer item as active?
or any successful implementation of DrawerNavigator inside StackNavigator ? ie. I want to use two screens from stack navigator as drawer items. And if we navigated through to that particular screen from home screen, the corresponding drawer item should be selected.
Not sure whether you have tried contentOptions or not, but this is what i have found from react-navigation document
contentOptions for DrawerItems
There are various property you can use with contentOptions
contentOptions: {
activeTintColor: '#e91e63',
itemsContainerStyle: {
marginVertical: 0,
},
iconContainerStyle: {
opacity: 1
}
}
From above snippet i guess for you activeTineColor might be useful.
I'm not sure about your intentions with the resetStack function.
If you use the following function instead, it's going to work:
navigateToScreen = (route) => {
const navigateAction = NavigationActions.navigate({
routeName: route.route.key
});
this.props.navigation.dispatch(navigateAction);
}
//...
<DrawerItems
{...this.props}
onItemPress={this.navigateToScreen}>
</DrawerItems>
It sets the new screen without stacking it up. It sets the activeTintColor though.
I omitted the params passing to make it simple.

Conditional Rendering not reliably setting state on different components

I have two navigation buttons (light version, and dark version) that I want to render on certain pages.
I tried setting the state in the constructor, and generating the link to the images based on the path of the page, but sometimes the wrong link to the image will generated. It seems as though it's getting the state based on the first page that was ever generated. For example, if "home" is supposed to have the light version of the button any other link I click will generate the light version of the logo, unless I refresh. If "about" is supposed to have the dark version of the logo, all other pages I click through will have the dark version, unless I refresh.
Why won't it generate properly while naturally clicking around and navigating through the different pages?
MenuButton.js
import React, { Component } from 'react';
export default class MenuButton extends Component {
constructor() {
super();
this.state = {
logo_url: ''
}
}
componentDidMount() {
let currentPath = window.location.pathname;
if (!currentPath.includes('about') && !currentPath.includes('news')
&& !currentPath.includes('work')) {
this.setState({ logo_url: `${require('../../assets/nav/logo-light.svg')}` });
} else {
this.setState({ logo_url: `${require('../../assets/nav/logo-dark.svg')}` });
}
}
render() {
return (
<div className="menu-btn--cntr">
<img src={this.state.logo_url} />
</div>
)
}
}
You don't need to use state and life cycle.
You can try something like below -
import React, { Component } from 'react';
export default class MenuButton extends Component {
constructor() {
super();
this.state = {
logo_url: ''
}
}
getButton() {
let currentPath = window.location.pathname;
let btnUrl = ''; // or set some default
if (!currentPath.includes('about') && !currentPath.includes('news')
&& !currentPath.includes('work')) {
// this.setState({ logo_url: `${require('../../assets/nav/logo-light.svg')}` });
btnUrl = `${require('../../assets/nav/logo-light.svg')}`;
} else {
// this.setState({ logo_url: `${require('../../assets/nav/logo-dark.svg')}` });
btnUrl = `${require('../../assets/nav/logo-light.svg')}`;
}
return btnUrl;
}
render() {
const btnUrl = this.getButton();
return (
<div className="menu-btn--cntr">
<img src={btnUrl} />
</div>
)
}
}

Interact with StackNavigator's navigationOptions from view inside of TabNavigator

How can I make navigationOptions interact with view inside of TabNavigator?
for example, from this question how do I access a component's state from static navigationOptions, I can make navigationOptions responsive to component's state.
But what if I want to make it responsive a step further, responsive to component's state inside of TabNavigator?
As image shows below, I want to make Next button enabled only if more than one checkbox get checked.
But there's no effect to provide headerRight in my component.
export class CreateGroupCamera extends Component {
static navigationOptions = ({ navigation }) => {
const { state, setParams, navigate } = navigation;
const params = state.params || {};
return {
headerTitleStyle: { alignSelf: 'center' },
headerRight: <Button title='Next' disabled={params.validator ? !params.validator() : true} />
}
}
}
I found an answer myself after try error 3 hours...
follow these steps you can have a workable version.
1. Instead of expose TabNavigator directly, wrap it.
const Tabs = TabNavigator({
...
});
export class TabView extends Component {
render() {
return (<Tabs />);
}
}
2. Pass callback function down with screenProps. When called, setParams() into navigationOptions.
export class TabView extends Component {
static navigationOptions = ({navigation}) => {
const { state, setParams, navigate } = navigation;
const params = state.params || {};
return {
headerRight = <Button title='Next' disabled={params.validator ? !params.validator() : true} />
};
}
_validator(validator) {
this.props.navigation.setParams({validator});
}
render() {
return (<Tabs screenProps={{ callback: this._validator } />);
}
}
3. Inside main component, pass function with screenProps.callback.
_validator() {
return this.selectedDevices().length > 0;
}
render() {
return (
<Checkbox onChecked={ () => {
this.props.screenProps.callback(this._validator);
}} />
);
}
result:

react native navigate with different files

As a newbie in react-native i'm trying find a way to use react-navigation with different files without a navbar to navigate (for example just click on a button in the login view to enter the main view, clicking on log out button return to login view).
With react-navigation i made a global component 'Nav' and i placed in the main AppRegistry call:
AppRegistry.registerComponent('myApp', () => Nav);
While in the Navigator.js:
export const Nav = StackNavigator({
Login: { screen: LoginView },
AddStream: { screen: AddStreamView },
});
When i run
react-native run-android
i get the first view loaded under a white space (it should be the navigator, that i don't want in my app)
Now how can i remove the Nav from the view and how can i change View onPress a button component in another file?
There is another package that fill better my desire?
From the docs, setting the the headerMode to none should do the trick. This will hide the header on all of the screens defined in that StackNavigator. Example:
const Nav = StackNavigator({
Login: { screen: LoginView },
AddStream: { screen: AddStreamView },
},{
headerMode: 'none',
});
If you want to control the visibility of the header so that it appears on some of the screens but not all of them, then you can set headerMode to screen and for each individual screen set [the navigation prop for header: { visible: false }. See docs here. For example, if you are creating the screen component in another file, it might look like this:
// In the main file:
const Nav = StackNavigator({
Login: { screen: LoginView },
AddStream: { screen: AddStreamView },
},{
headerMode: 'screen',
});
// Hidden for your Login screen:
export default class LoginView extends Component {
static navigationOptions = {
header: { visible: false }
}
// etc...
}
// Visible for your AddStream screen:
export default class AddStreamView extends Component {
static navigationOptions = {
header: { visible: true }
}
// etc...
}
Edit: missed the second part of your question.
For navigating to a different screen, you really should go through the Hello World tutorial as it explains it. Specifically, this section. Relevant example code from the tutorial:
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat')}
title="Chat with Lucy"
/>
</View>
);
}
In your case, 'Chat' would be either 'Login' or 'AddStream'.

Categories