Passing props from a root component through a BottomTabNavigator - react-native

My app root component looks like this:
export default class App extends React.Component {
render() {
<RootTabs doThings={this.doThings} />
}
}
The RootTabs component is created by createBottomTabNavigator from react-navigation:
const RootTabs = createBottomTabNavigator({
Movies: {
screen: Movies
},
Actors: ... // etc
})
My problem is, I would like to transmit data (as a prop if possible) from the root component (App) to the Movies component, but Movies do not receive the doThings prop.
How to transmit props through a BottomTabNavigator to children?
If that's not possible, what would be the most simple way for a children component to call a method on a root component, when they are separated by a BottomTabNavigator?

Try using screenProps
screenProps is documented on this page
Answered referred from here
Minimal Example would be
import React, { Component } from 'react'
import { AppRegistry, Button, Text, View } from 'react-native'
import { StackNavigator } from 'react-navigation'
class HomeScreen extends Component {
render() {
const { navigation, screenProps } = this.props
return (
<View>
<Text>Welcome, {screenProps.user.name}!</Text>
<Button onPress={() => navigation.navigate('Profile')} title="Go to Profile" />
</View>
)
}
}
class ProfileScreen extends Component {
render() {
const { navigation, screenProps } = this.props
return (
<View>
<Text>My Profile</Text>
<Text>Name: {screenProps.user.name}</Text>
<Text>Username: {screenProps.user.username}</Text>
<Text>Email: {screenProps.user.email}</Text>
<Button onPress={() => navigation.goBack()} title="Back to Home" />
</View>
)
}
}
const AppNavigator = StackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
})
class MyApp extends Component {
render() {
const screenProps = {
user: {
name: 'John Doe',
username: 'johndoe123',
email: 'john#doe.com',
},
}
return (
<AppNavigator screenProps={screenProps} />
)
}
}
export default MyApp
AppRegistry.registerComponent('MyApp', () => MyApp);
HomeScreen and ProfileScreen are components defined as screens for AppNavigator.
In the example above, I am passing the user data from the top-level, root component MyApp to both HomeScreen and ProfileScreen.
Since there is a AppNavigator between MyApp and the screen components, we will need to pass the user to screenProps prop of AppNavigator, so that the AppNavigator will pass it down to the screens. Any other prop except screenProps will not be passed down.
MyApp <-- user data here .
AppNavigator <-- the StackNavigator, the middle man. must use screenProps to pass user data down .
HomeScreen <-- will receive user data from this.props.screenProps.user instead of this.props.user .
ProfileScreen <-- same as HomeScreen

Related

How to nest a component wrapping a stack navigator inside a tab navigator react

Current Behavior
I am trying to have a tab navigator, where one of the screens/tabs has a component, that, among other stuff, has a stack navigator in it.
However, I currently get a 'No "routes" found in navigation state' error.
How to reproduce
The code I'm currently running can also be found as a snack.
Code:
import * as React from 'react';
import { Button, Text, View, StyleSheet } from 'react-native';
import { createAppContainer, createStackNavigator, createBottomTabNavigator } from 'react-navigation';
class ScreenA extends React.Component {
static navigationOptions = {
tabBarLabel: 'A',
};
render() {
return (
<View>
<Text>Screen A</Text>
</View>
);
}
}
class SettingsHome extends React.Component {
render() {
return (
<Button onPress={() => this.props.navigation.navigate('SettingsScreenA')}>
<Text>Navigate to Settings A</Text>
</Button>
);
}
}
class SettingsScreenA extends React.Component {
render() {
return (
<View>
<Text>Settings A</Text>
<Button onPress={() => this.props.navigation.navigate('SettingsA')}>
<Text>Back to SettingsHome</Text>
</Button>
</View>
);
}
}
const SettingsStackNavigator = createStackNavigator({
SettingsHome: { screen: SettingsHome },
SettingsScreenA: { screen: SettingsScreenA }
})
class SettingsScreen extends React.Component {
static navigationOptions = {
tabBarLabel: 'Settings',
}
render() {
return (
<View>
<Text>Some other components...</Text>
<SettingsStackNavigator navigation={this.props.navigation}/>
</View>
);
}
}
const RootTabNavigator = createBottomTabNavigator({
ScreenA: { screen: ScreenA },
Settings: {screen: SettingsScreen },
});
const Navigation = createAppContainer(RootTabNavigator);
export default class App extends React.Component {
render() {
return (
<Navigation />
);
}
}
in this button you want to navigate to SettingsA
<Button onPress={() => this.props.navigation.navigate('SettingsA')}>
<Text>Back to SettingsHome</Text>
</Button>
but you defined the route of Settings screen as Settings
const RootTabNavigator = createBottomTabNavigator({
ScreenA: { screen: ScreenA },
Settings: {screen: SettingsScreen },
});
you have to fix it by change the SettingsA as route name in tab navigator
const RootTabNavigator = createBottomTabNavigator({
ScreenA: { screen: ScreenA },
SettingsA: {screen: SettingsScreen },
});

Logging User after Persist Rehydration

I am trying to login user after Rehydration in react-native. I am not sure how to go about redirecting user after I have data in the store.
Here is how I setup my PersistGate in my index.js file:
class AppRedux extends React.Component {
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor} loading={<SplashScreen />}>
<App />
</PersistGate>
</Provider>
);
}
}
AppRegistry.registerComponent(appName, () => AppRedux);
SplashScreen.js:
class SplashScreen extends Component {
constructor(props) {
super(props);
props.navigation.navigate(RouteNames.Home);
}
render() {
return <View style={{ flex: 1, backgroundColor: "green" }}></View>;
}
}
const mapStateToProps = state => {
return {
rehydrated: state.user.rehydrated
};
};
export default connect(mapStateToProps)(SplashScreen);
The problem with this approach is that I am not able to navigate from splash screen. I get Navigation prop is undefined.
"loading" prop in "PersistGate" is just a dummy component to show loader until Persist Rehydration . "App"(navigation component) will load after Rehydration where you can receive "navigation" prop and can access store.
Try this like described in official site Auth Flow
Create AuthLoading Screen and set navigation from there
App.js
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createAppContainer(
createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
)
);

React-Navigation this.props.navigation.navigate undefined is not an object

I am using react-navigation but my codes return ; this undefined is not an object (evaluating '_this2.props.navigation.navigate)
how can i fix this error?
myCodes;
import React, { Component } from 'react';
import Login from './components/LoginScreen/LoginScreen';
import AddNewUser from './components/AddNewUserScreen/AddNewUserScreen';
import { createStackNavigator } from 'react-navigation';
class App extends Component{
render(){
return(
<Login />
);
}
}
const RootStack = ({ createStackNavigator }) => (
{
Login: Login,
AddNew: AddNewUser,
});
export default App;
LoginScreen.js
<TouchableOpacity style={styles.ButtonContainer} onPress={()=> this.props.navigation.navigate('AddNew')}>
<Text style={styles.buttonText} >GİRİŞ</Text>
</TouchableOpacity>
You should start your app by calling RootStack like this:
class App extends Component{
render(){
return(
<RootStack />
);
}
}
also you can set initialRout in stack like this:
const RootStack = createStackNavigator({
Login: Login,
AddNew: AddNewUser,
}, { initialRouteName: 'Login'} )
Now, as you define, your app will start with login page and it has this.props.navigation by itself so you can use this.props.navigation.navigate('AddNew') without error.
But if you need to use navigation from a component, you have to send this.props.navigation from parent to component like this:
<YourComponent navigation={this.props.navigation}/>
Then you can use navigation in YourComponent component.
I hope this can help you

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.