how to change header navigation in the same component? - react-native

my navigation initializeRoute screen is main.js
main.js have a onAuthStateCHange to detect a changes a current user
if current user is loggend main render component "ChatScreen"
else main render a compoment "LoginScreen"
the problem is chatScreen and LoginScreen have the same navigation header bar (main header bar)
I need the loginscreen does not have a header and the chatScreen does
i have tried use a
static navigationOptions = {header: null} in login and chat, but de header we can se is header of main.js
if i set header null on main the chatScreen is without header too
class App extends React.Component {
constructor() {
super();
this.unsubscriber = null;
this.state = {
user: null,
};
}
componentDidMount() {
this.unsubscriber = firebase.auth().onAuthStateChanged((user) => {
this.setState({ user });
});
}
render() {
if (!this.state.user) {
return <Login />;
}
return <Chat />;
}
}

The solution is the following in the main.js screen, you leave the navigation as you have it.
// main.js
static navigationOptions = ({ navigation }) => {
return { title: "", header: null
};
};
Then in the ChatScreen screen you personalize the header without depending on the react-navigation library, you do it in a personalized way.
// Chat.js
class Chat extends Component {
render() {
return (
<SafeAreaView>
{/*header*/}
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text>Chat</Text>
</View>
{/*body*/}
<View>
</View>
</SafeAreaView>
);
}
}
export default Chat;
The child component Chat must receive the navigation properties
<Chat navigation={this.props.navigation} />
and in turn the chat children should be passed.
<SonChat navigation={this.props.navigation} />

Related

React Native: How to show screen state variable updates in HeaderRight custom component using Context Api

I'm new to React Native and I want to pass state variable cartTotal from Cart component to the custom HeaderRight CartCounter component so that when I update cartTotal variable in Cart component changes are visible in CartCounter component where I show it. I don't wanna use Redux, but I know that is possible with Context Api. I did a long search to find an example similar to mine but I didn't find it. Can you show me how to use it in my case?
Here my code
Cart Component
export default class Cart extends React.Component {
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerTransparent: true,
headerTintColor: "black",
headerRight: () => {
return <CartCounter cartTotal={this.state.cartTotal} />
}
};
}
constructor (props) {
super(props);
this.state = {
cartTotal: 0
}
}
...
...
...
CartCounter Component
export default class CartCounter extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<TouchableOpacity onPress={ this.onPress }>
<View style={ styles.root }>
<Text style={ styles.total }>{ cart total updates I want to see } </Text>
</View>
</TouchableOpacity>
)
}
}
...
...
...
Thanks in advance
use react context https://reactjs.org/docs/context.html to create a global state and seter

How do I navigate to a sub-screen after the first componentDidMount?

If I have a screen that receives route params, does some processing, and then re-routes to a sub-screen, this works if the screen was previously mounted but I get the following error if I try this after the first componentDidMount:
The action 'NAVIGATE' with payload {"name":"Chat","params":{"name":"Person2"}} was not handled by any navigator.
Do you have a screen named 'Chat'?
If you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.
This is a development-only warning and won't be shown in production.
[...]
Here are the highlights:
Tab navigator (App) with home (HomeScreen) and chats (ChatsScreenStack) tabs.
The chats tab is a stack navigator with a chats list to list all chats (ChatsListScreen) and a chat screen to show a particular chat (ChatScreen).
The chats tab stack navigator (ChatsScreenStack) has a componentDidUpdate which checks if the name prop has been updated, and, if so, it navigates to the chat tab.
The chats tab stack navigator also has a constructor which checks if it was created with a name prop, and, if so, it saves it off to a field and does the same navigation as above in componentDidMount.
Item 3 works but Item 4 doesn't work. Is this because react-navigation hasn't built up its navigation state at the time of the first componentDidMount? If so, how do I get a callback when react-navigation is ready?
Below is a reproduction (Snack link, Github link). If you launch, and click on ChatsTab, click back on HomeTab, and then click on the button it works. However, if you launch, and immediately click on the HomeTab button, it gives the error (in development mode; on the snack, it will navigate to the chats list rather than the chat screen).
import * as React from 'react';
import { Button, FlatList, Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
class ChatScreen extends React.Component {
render() {
return (
<View style={{ padding: 10 }}>
<Text>Chat with {this.props.route.params.name}</Text>
</View>
);
}
}
class ChatsListScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<FlatList
data={[ {name: "Person1", key: "1"}, {name: "Person2", key: "2"}]}
renderItem={(data) => {
return (
<View key={data.item.key} style={{ margin: 10 }}>
<Button
title={data.item.name}
onPress={() => this.props.navigation.navigate("Chat", { name: data.item.name })}
/>
</View>
);
}}
/>
</View>
);
}
}
const ChatsStack = createStackNavigator();
class ChatsScreenStack extends React.Component {
constructor(props) {
super(props);
if (props.route && props.route.params && props.route.params.name) {
this.pendingReroute = props.route.params.name;
}
}
componentDidMount() {
if (this.pendingReroute) {
this.props.navigation.navigate("Chat", { name: this.pendingReroute });
}
}
componentDidUpdate(prevProps) {
let updated = false;
if (this.props.route && this.props.route.params.name) {
updated = true;
if (prevProps.route && prevProps.route.params && prevProps.route.params.name == this.props.route.params.name) {
updated = false;
}
}
if (updated) {
this.props.navigation.navigate("Chat", { name: this.props.route.params.name });
}
}
render() {
return (
<ChatsStack.Navigator>
<ChatsStack.Screen name="Chats" component={ChatsListScreen} />
<ChatsStack.Screen name="Chat" component={ChatScreen} />
</ChatsStack.Navigator>
);
}
}
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button
title="Navigate to Person2"
onPress={() => this.props.navigation.navigate("ChatsTab", { name: "Person2" })}
/>
</View>
);
}
}
const Tabs = createBottomTabNavigator();
export default class App extends React.Component {
render() {
return (
<NavigationContainer>
<Tabs.Navigator>
<Tabs.Screen name="HomeTab" component={HomeScreen} />
<Tabs.Screen name="ChatsTab" component={ChatsScreenStack} />
</Tabs.Navigator>
</NavigationContainer>
);
}
}
Yes, this was related to react-navigation not being ready in componentDidMount. I needed to handle the focus event:
class ChatsScreenStack extends React.Component {
constructor(props) {
super(props);
if (props.route && props.route.params && props.route.params.name) {
this.pendingReroute = props.route.params.name;
}
}
componentDidMount() {
this.props.navigation.addListener(
"focus",
this.onFocus
);
}
onFocus = () => {
if (this.pendingReroute) {
const name = this.pendingReroute;
this.pendingReroute = null;
this.props.navigation.navigate("Chat", { name: this.pendingReroute });
}
}
[...]

How to pass a function as a prop in React-Native

I'm currently working on an app in React-Native and it includes DrawerNavigation, SwitchNavigation and AppContainer. There is a method at header.js that i need to use in order to make the drawer functionable (toggleDrawer())
I've tried passing the function at the DrawerNavigator but it didnt work.
export default class Header extends React.Component {
render() {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => {
this.props.navigation.toggleDrawer();
}}
>
<Image
source={require("/Users/Rron/AnketaApp/assets/hamburger-
icon.jpg")}
style={styles.imageStyle}
/>
</TouchableOpacity>
</View>
);
}
}
});
export default class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
let drawerLabel = "Home";
return { drawerLabel };
};
render() {
return (
<View style={styles.container}>
<Header {...this.props}/>
<ScrollView>
<Content />
</ScrollView>
</View>
);
}
}
export default class DrawerNavigator extends React.Component {
render() {
return <AppContainer />;
}
}
const AppDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: HomeScreen
},
Anketa: {
screen: AnketaScreen
}
}
);
const AppContainer = createAppContainer(
createSwitchNavigator({
Introduction: {
screen: IntroductionScreen
},
Drawer: {
screen: AppDrawerNavigator``
}
})
);
The error says
this.props.navigation.toggleDrawer is not a function and its not
defined.
What you can do is import { DrawerActions } from 'react-navigation-drawer' and use it as it says in the docs.
this.props.navigation.dispatch(DrawerActions.toggleDrawer());
Also make sure that you components are inside the navigation.

StackNavigator through Component gives undefined error

I was trying to use StackNavigator for navigation and it works when I use it to go from one screen to the other as explained here. But when I try to have a subcomponent to navigate through itself, the navigation doesn't seem to work and I couldn't find any solution to it.
As given in the code below, I'm trying to use the Test Component in which there is a button that can be clicked to move from HomeScreen to ChatScreen.
I'm pretty sure the solution is something basic, but I really can't find it anywhere.
Here's my code:
import React from 'react';
import {
AppRegistry,
Text,
View,
Button
} from 'react-native';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
let userName = 'Ketan';
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat', { user: userName })}
title={"Chat with " + userName}
/>
<Test />
</View>
);
}
}
class ChatScreen extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: `Chat with ${navigation.state.params.user}`,
});
render() {
const { params } = this.props.navigation.state;
return (
<View>
<Text>Chat with {params.user}</Text>
</View>
);
}
}
class Test extends React.Component {
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Button
onPress={() => navigate('Chat', { user: 'TestBot' })}
title={'This is a test'}
/>
</View>
)
}
}
const NavApp = StackNavigator({
Home: { screen: HomeScreen },
Chat: { screen: ChatScreen },
});
AppRegistry.registerComponent('NavApp', () => NavApp);
Here's the error I'm getting:
Here's the demo to test: https://snack.expo.io/HyaT8qYob
I hope my question is clear enough of what I mean.
Since your Test component does not belong to navigation stack it doesn't have the navigation prop. You can do couple of things.
Simple one is to pass the navigation to the child component like the example below.
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat', { user: userName })}
title={"Chat with " + userName}
/>
<Test navigation={this.props.navigation} />
</View>
);
The second option is, you can use withNavigation from react-navigation. You can find more details about it here
import { Button } 'react-native';
import { withNavigation } from 'react-navigation';
const MyComponent = ({ to, navigation }) => (
<Button title={`navigate to ${to}`} onPress={() => navigation.navigate(to)} />
);
const MyComponentWithNavigation = withNavigation(MyComponent)
withNavigation
withNavigation is a higher order component which passes the
navigation prop into a wrapped component. It's useful when you
cannot pass the navigation prop into the component directly, or
don't want to pass it in case of a deeply nested child.

How to access the react-navgiation inside of a functional component or class component which doesnt have access to this.props.navigation?

Im doing this inside the react native platform using expo.
I want to display the list of items ( ListItems.js) All_Employees_screen.js . These items are being rendered via a functional component, I want to have a onRowPress() handler to so that upon clicking it i can navigate it to another view, but I dont know how to do it on react-navigation ?
Or since the new functional component can be a class component( this would be better ) how can i access the navigation thing inside it ?
AllProperties.js
import _ from 'lodash';
import React, {
Component
} from 'react';
import {
Button,
ListView,
ScrollView
} from 'react-native';
import ListItem from './ListItem';
import { connect } from 'react-redux';
import { propertiesFetch } from '../../actions';
// import { FormLabel, FormInput } from 'react-native-elements'
class AllPropertiesScreen extends React.Component {
componentWillMount(){
this.props.propertiesFetch();
this.createDataSource(this.props);
}
// we do this componentWillMount & componentWillReceiveProps (nextProps) thing twice, coz once the component is
// loaded it loads all teh values but when user hits another view like Create property, The Property data still exists
// in the global state object,
// we could move all the dc dataSource code into componentWillReceiveProps but its actually gonna benefit us
// if we make sure that we try to build our data source both when the component first loads up
// & when second time after we go back and forth other compoennts.
componentWillReceiveProps(nextProps){
// nextProps are the next set of props that this component will be rendered with
// this.props is still the old set of props
this.createDataSource(nextProps);
}
createDataSource({ properties }){
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(properties);
}
static navigationOptions = ({ navigation }) => {
const {state, setParams} = navigation;
return {
title: 'All Emplooyee',
headerRight: (
<Button
title='Add'
// onPress={() => setParams({ mode: isInfo ? 'none' : 'info'})}
onPress={() => navigation.navigate('createProperty')
}
/>
),
};
};
goBack(){
console.log('65 - go Back clicked');
}
renderRow(property){
// console.log('67-AllPropertiesScreen =', property);
return <ListItem property={property}
onPress={() => { console.log('65 - go Back clicked') }}
/>;
}
render() {
console.log('72-AllPropertiesScreen this.props', this.props );
return(
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
);
}
}
const mapStateToProps = state => {
console.log('83 - AllPropertiesScreen state. properties', state );
const properties = _.map(state.properties, (val, uid ) => {
return { ...val, uid }; // { shift: 'Monday'}
});
return { properties };
};
export default connect(mapStateToProps, {propertiesFetch}) (AllPropertiesScreen);
ListItem.js
import React, { Component } from 'react';
import { Text, TouchableWithoutFeedback, View } from 'react-native';
class ListItem extends Component {
// onRowPress(){
// Actions.employeeEdit({ employee: this.props.employee });
// }
render(){
const { agent_name, cell, address } = this.props.property;
console.log('14- ListItem ', this.props);
return (
<View>
<CardSection>
<Text style={styles.titleStyle}>
name
</Text>
<Text style={styles.titleStyle}>
cell
</Text>
<Text style={styles.titleStyle}>
address
</Text>
</CardSection>
</View>
);
}
}
const styles = {
titleStyle: {
fontSize: 18,
paddingLeft: 15
}
}
export default ListItem;
//
main.js ( this is where I have all the navigation paths hookedup.
class App extends React.Component {
render() {
const MainNavigator = TabNavigator({
// auth: { screen : AuthScreen },
// review: { screen: ReviewScreen },
// signup: { screen : SignupScreen },
followup: { screen: FollowupScreen }, welcome: { screen : WelcomeScreen },
auth: { screen : AuthScreen },
signup: { screen : SignupScreen },
main: {
screen: TabNavigator ({
followup: { screen: FollowupScreen },
map: { screen: MapScreen },
deck: { screen: DeckScreen },
settings : {
screen: StackNavigator ({
settings: { screen: SettingsScreen },
// settings: { screen: SettingsScreen },
UserProfile: { screen: UserProfileScreen },
HelpSupport: { screen: HelpSupportScreen },
Notifications: { screen: NotificationsScreen },
Signout: { screen: SignoutScreen } // not working, Navigation object not accessible inside the component
}) //screen: StackNavigator ({
},
followup : {
screen: StackNavigator ({
followup: { screen: FollowupScreen },
allProperties: { screen: AllPropertiesScreen },
createProperty: { screen: PropertyCreateScreen },
Red: { screen: RedPriorityScreen }, // not working, Navigation object not accessible inside the component
GreyPriority: { screen: GreyPriorityScreen },
}) //screen: StackNavigator ({
},
draw: {
screen: DrawerNavigator ({
drawin: { screen: DrawScreen },
}) //screen: StackNavigator ({
}
}) //screen: TabNavigator
}
}, {
navigationOptions: {
tabBarVisible: false
},
lazy: true
});
return (
<Provider store={store}>
<View style={styles.container}>
<MainNavigator />
</View>
</Provider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
// alignItems: 'center',
justifyContent: 'center',
},
});
Expo.registerRootComponent(App);
Solution suggested by #Matt but as soon as I put the navigation={this.props.navigation} it complains. undefined is not an object ( evaluating this.props.navigation )
renderRow(property){
return (
<ListItem
property={property}
navigation={this.props.navigation}
onPress={() => {
console.log( '70-on Press inside renderRow ');
}}/>
);
}
If the component is not a screen you have to import the navigation.
Try this:
import React from 'react';
import { Button } 'react-native';
import { withNavigation } from 'react-navigation';
class MyBackButton extends React.Component {
render() {
return <Button title="Back" onPress={() => { this.props.navigation.goBack() }} />;
}
}
// withNavigation returns a component that wraps MyBackButton and passes in the
// navigation prop
export default withNavigation(MyBackButton);
For more info check out
https://reactnavigation.org/docs/connecting-navigation-prop.html
This answer was written for old version of react-navigation V1
I had the same exact problem, and I found out that this.props.navigation is injected only in components that are registered as screen in StackNavigator or TabbNavigator.
but in general you can use navigate from NavigationActions class (source here https://v1.reactnavigation.org/docs/navigation-actions.html#navigate)
note: NavigationActions.navigate receives parameters in different way but works the same way.
so this working for me
import { NavigationActions } from 'react-navigation';
let {navigate} = NavigationActions;
renderRow(property) {
return (
<ListItem
property={property}
onPress={() => { navigate({
routeName: 'OtherRoute'
});
}}/>
);
}
<MyComponent navigation={this.props.navigation}/>
Main problem is here. You didn't define your prop navigation in component. You should add this.
Here's how you can use navigation.navigate inside a functional component:
import { Text, TouchableHighlight } from 'react-native';
const MyComponent = ({ navigation }) => (
<TouchableHighlight
onPress={() => navigation.navigate('OtherRoute')}
underlayColor="blue"/>
<Text>Click to Navigate!</Text>
</TouchableHighlight>
);
export default MyComponent;
When you render MyComponent, you will need to pass navigation as a prop. For example, assume HomeContainer is a screen component:
import React from 'react';
import MyComponent from './MyComponent';
export default HomeContainer extends React.Component {
render() {
return (
<MyComponent navigation={this.props.navigation}/>
);
}
}
Change your renderRow method to the following:
renderRow(property) {
return (
<ListItem
property={property}
onPress={() => { this.props.navigation.navigate('OtherRoute'); }}/>
);
}
where 'OtherRoute' is the name of the route you want to navigate to for that row.