Passing props to tabNavigator's screens - react-native

In my app, I want to pass some data in my createMaterialTopTabNavigator (const NewTab) to both it's child screens (Graphical and Tabular). The data in child screens changes on the basis of the dropdown value in the custom header that will be above createMaterialTopTabNavigator. As soon as the value from the dropdown is selected, it will trigger a fetch request in both the Graphical and Tabular Screen.
Here is my App.js where I am routing all my screens.
const NewTab = createMaterialTopTabNavigator({
Graphical: Graphical,
Tabular: Tabular
});
const DailyStack = createStackNavigator({
Dashboard,
Daily,
Login,
SalesDashboard,
About : {
screen: NewTab,
navigationOptions : {
header : <CustomHeader />
}
}
})
const MonthlyStack = createStackNavigator({
Monthly: Monthly
})
const RangeStack = createStackNavigator({
Range: Range
})
export const BottomTabNavigation = createBottomTabNavigator({
Daily: {
screen: DailyStack
},
Monthly: {
screen: MonthlyStack
},
Range: {
screen: RangeStack
}
})
const DashboardStackNavigator = createStackNavigator({
BottomTabNavigation:BottomTabNavigation,
}, {
headerMode: 'none'
})
export const AppDrawerNavigator = createDrawerNavigator({
DashboardStackNavigator,
Login: {
screen: Login
},
Logout: {
screen: Logout
}
})
const OpenNav = createSwitchNavigator({
Splash: {screen: SplashScreen},
Login: { screen: Login },
Home: { screen: Home }
})
const AppNavigator = createStackNavigator({
OpenNav,
AppDrawerNavigator: { screen: AppDrawerNavigator },
ClaimsDashboard: ClaimsDashboard
},{
headerMode:'none'
});
class App extends Component {
constructor(){
super();
console.disableYellowBox = true;
}
render() {
return (
<AppContainer />
);
}
};
const AppContainer = createAppContainer(AppNavigator);
export default App;
CustomHeader.js
import React, { Component } from 'react'
import { Text, View, Picker } from 'react-native'
import Icon from 'react-native-vector-icons/AntDesign'
export default class CustomHeader extends Component {
constructor(props) {
super(props)
this.state = {
user: ''
}
}
updateUser = (user) => {
this.setState({ user: user })
}
render() {
return (
<View>
<Icon name="arrowleft" size={30} onPress={navigation.goBack()} />
<View>
<Picker selectedValue = {this.state.user} onValueChange = {this.updateUser}>
<Picker.Item label = "Steve" value = "steve" />
<Picker.Item label = "Ellen" value = "ellen" />
<Picker.Item label = "Maria" value = "maria" />
</Picker>
</View>
</View>
)
}
}
Considering all files included in App.js, I tried using screenprops for the same, but is not sure how to do it.
In dire need for solution. Please help.

Yes, you can use screenProps here as I made below, but I strongly recommend using third party to manage state like redux, mobx, etc... or simply using context.
class DailyStackWithData extends React.Component {
static router = DailyStack.router
state = {
user: 'steve'
}
setUser = user => this.setState({ user })
componentDidMount(){
this.props.navigation.setParams({ setUser: this.setUser });
}
render(){
const { user } = this.state;
const { navigation } = this.props;
return (<DailyStack screenProps={user} navigation={navigation}/>)
}
}
export const BottomTabNavigation = createBottomTabNavigator({
Daily: {
screen: DailyStack
},
...
});
CustomHeader.js
import React, { Component } from 'react';
import { Text, View, Picker } from 'react-native';
import Icon from 'react-native-vector-icons/AntDesign';
import { withNavigation } from "react-navigation";
class CustomHeader extends Component {
constructor(props) {
super(props)
this.state = {
user: ''
}
}
updateUser = user => {
const setUser = this.props.navigation.getParam('setUser', () => {});
setUser(user);
this.setState({ user: user });
}
render() {
return (
<View>
<Icon name="arrowleft" size={30} onPress={()=>{}} />
<View>
<Picker selectedValue = {this.state.user} onValueChange = {this.updateUser}>
<Picker.Item label = "Steve" value = "steve" />
<Picker.Item label = "Ellen" value = "ellen" />
<Picker.Item label = "Maria" value = "maria" />
</Picker>
</View>
</View>
)
}
}
export default withNavigation(CustomHeader)
and in Graphical or Tabular screen you can get that variable by screenProps props. I've made an example here

Related

Why is 'this.props.navigation' unavailable to screen component?

I have a drawer navigator that has a stack navigator nested inside it as one of the screen options. I have no issues navigating to any screens in the drawer, but when I try to navigate to another screen in the stack navigator, I dont have access to this.props.navigation. Im confused because the screen is declared in my navigator setup.
AppNavigator:
// all imports
const InboxStack = createStackNavigator(
{
Inbox: {
screen: HomeScreen
},
Conversation: {
screen: ConversationScreen
}
},
{
headerMode: "none",
initialRouteName: "Inbox"
}
);
const MainNavigator = createDrawerNavigator(
{
Inbox: InboxStack,
Second: { screen: SecondScreen },
Third: { screen: ThirdScreen }
},
{
drawerPosition: "left",
initialRouteName: "Inbox",
navigationOptions: ({ navigation }) => ({
title: "Drawer Navigator Header",
headerTitleStyle: {
color: "blue"
},
headerLeft: <Text onPress={() => navigation.toggleDrawer()}>Menu</Text>
})
}
);
const WrapperStackNavigator = createStackNavigator({
drawerNav: MainNavigator
});
const AppContainer = createAppContainer(
createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: WrapperStackNavigator,
Login: LoginScreen,
Register: RegisterScreen
},
{
initialRouteName: "AuthLoading"
}
)
);
export default AppContainer;
utilization of navigation prop in ConversationScreen:
import React from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import { loadConversation } from "../actions";
import MessagesList from "../components/MessagesList.js";
class ConversationScreen extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.loadConversation();
}
loadConversation() {
this.props.loadConversation(
this.props.navigation.state.params.convoId // this works!
this.props.navigation.getParams("convoId") // this does not :(
);
}
render() {
return (
<View
style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
<Text>Conversation screen</Text>
<MessagesList messages={this.props.messages} />
</View>
);
}
}
const mapStateToProps = ({ conversation, auth }) => {
const { messages } = conversation;
const { user } = auth;
return { messages, user };
};
const mapDispatchToProps = {
loadConversation
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ConversationScreen);
The issue exists with ConversationScreen, I dont have access to the navigation prop, but docs say if its declared in the navigator it should be be passed the navigation prop. Every call using this.props.navigation errors with ... is not a function, ... is undefined
... being this.props.navigation.navigate, this.props.navigation.getParams, etc
After checking the props object through alert(this.props) I verified I had access to the navigation object, even though it appeared to be undefined, ultimately using
componentDidMount() {
loadConversation() {
this.props.loadConversation(
this.props.navigation.state.params.convoId
);
}
}
This got me where I needed to be. although it would have been nice to just use this.props.navigation.getParams()

How to use AsyncStorage in createBottomTabNavigator with React Navigation?

I work with react-navigation v3 and I want to use AsyncStorage in createBottomTabNavigator for checking if user logged.
I save key to Stoage in LoginScreen:
await AsyncStorage.setItem('#MyStorage:isLogged', isLogged);
And I want to use AsyncStorage in my stack (TabStack):
const TabStack = createBottomTabNavigator(
{
Home: { screen: HomeScreen, },
// I need isLogged key from AsyncStorage here!
...(false ? {
Account: { screen: AccountScreen, }
} : {
Login: { screen: LoginScreen, }
}),
},
{
initialRouteName: 'Home',
}
);
How I can do it?
My environment:
react-native: 0.58.5
react-navigation: 3.3.2
The solution is: create a new component AppTabBar and set this in tabBarComponent property
const TabStack = createBottomTabNavigator({
Home: { screen: HomeScreen, },
Account: { screen: AccountScreen, }
},
{
initialRouteName: 'Home',
tabBarComponent: AppTabBar, // Here
});
And AppTabBar component:
export default class AppTabBar extends Component {
constructor(props) {
super(props);
this.state = {
isLogged: '0',
};
}
componentDidMount() {
this._retrieveData();
}
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('isLogged');
if (value !== null) {
this.setState({
isLogged: value,
});
}
} catch (error) {
// Error retrieving data
}
};
render() {
const { navigation, appState } = this.props;
const routes = navigation.state.routes;
const { isLogged } = this.state;
return (
<View style={styles.container}>
{routes.map((route, index) => {
if (isLogged === '1' && route.routeName === 'Login') {
return null;
}
if (isLogged === '0' && route.routeName === 'Account') {
return null;
}
return (
<View /> // here your tabbar component
);
})}
</View>
);
}
navigationHandler = name => {
const { navigation } = this.props;
navigation.navigate(name);
};
}
You don't need to do that, just may want to check for a valid session in the login screen.
You need to create 2 stacks, one for the auth screens and your TabStack for logged users:
const TabStack = createBottomTabNavigator({
Home: { screen: HomeScreen, },
Account: { screen: AccountScreen, }
},
{
initialRouteName: 'Home',
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
});
const stack = createStackNavigator({
Home: {screen: TabStack},
Login: { screen: LoginScreen, }
});
and then check for a valid session in LoginScreen in the method componentDidMount.
class LoginScreen extends Component {
componentDidMount(){
const session = await AsyncStorage.getItem('session');
if (session.isValid) {
this.props.navigate('home')
}
}
}
In your Loading screen, read your login state from AsyncStorage, and store it in your Redux store, ( or any sort of global data sharing mechanism of your choice ) - I'm using redux here, then read this piece of data in your Stack component like the following:
import React from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import { createStackNavigator } from "react-navigation";
class Stack extends React.Component {
render() {
const { isLoggedIn } = this.props.auth;
const RouteConfigs = {
Home: () => (
<View>
<Text>Home</Text>
</View>
),
Login: () => (
<View>
<Text>Login</Text>
</View>
)
};
const RouteConfigs_LoggedIn = {
Home: () => (
<View>
<Text>Home</Text>
</View>
),
Account: () => (
<View>
<Text>Account</Text>
</View>
)
};
const NavigatorConfig = { initialRouteName: "Login" };
const MyStack = createStackNavigator(
isLoggedIn ? RouteConfigs_LoggedIn : RouteConfigs,
NavigatorConfig
);
return <MyStack />;
}
}
const mapStateToProps = ({ auth }) => ({ auth });
export default connect(mapStateToProps)(Stack);

Passing Navigator object in renderItem

I'm struggling to make Navigator object visible in List Component.
Here the code explained: as you can see in RootDrawer, I have Concept component. It simply shows a list of items based on a id passed in param.
Each item points to another Concept with another id, but I get
undefined is not an object (evaluating 'navigation.navigate')
when I press on that <RippleButton> with onPress={() => navigation.navigate('Concept',{id:12}). The problem here I'm not passing the Navigation object correctly. How can I solve?
main Navigator drawer
const RootDrawer = DrawerNavigator(
{
Home: {
screen: StackNavigator({
Home: { screen: Home }
})
},
Search: {
screen: StackNavigator({
Cerca: { screen: Search },
Concept: { screen: Concept },
})
}
}
);
Concept component
export default class Concept extends Component {
loading = true
static navigationOptions = ({ navigation,state }) => ({
headerTitle: "",
title: `${navigation.state.params.title}` || "",
})
constructor(props) {
super(props);
}
async componentDidMount(){
}
render() {
const { params } = this.props.navigation.state;
const id = params ? params.id : null;
const { t, i18n, navigation } = this.props;
const Concept = DB.get(id)
return (
<View>
<ScrollView>
<List data={Concept.array|| []} title={"MyTile"} navigation={navigation} />
</ScrollView>
</View>
);
}
}
List Component
class List extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { navigation } = this.props;
}
_renderItem = (item,navigation) => (
<RippleButton
id={item.id}
onPress={() => navigation.navigate('Concept', { id: 12, otherParam: 'anything you want here'})}> //this line throws the error
<Text>{item.Term}</Text>
</RippleButton>
)
render() {
const { navigation } = this.props;
return (
<View>
<FlatList
data={this.props.data}
renderItem={({item}) => this._renderItem(item, navigation)}
/>
</View>)
}
}
Instead of passing the navigation prop, you can try using the withNavigation HOC.
Where your List Component is defined:
import { withNavigation } from 'react-navigation`
....
export default withNavigation(List)
Now the List component will have access to the navigation prop

React Navigation - Tab Navigation - Getting different screens from only one class

It is possible to change between tabs without having more then one class?
On my code I have a class that returns multiple components, and I want my TabNavigator to switch between these componentes, not between classes like they have in the React Navigation docs (https://reactnavigation.org/docs/tab-based-navigation.html).
class Monument extends Component{
render(){
const {navigate} = this.props.navigation;
const { data } = this.props.navigation.state.params;
return (
<MonumentMaker category={'Castelos'} navigate={navigate} data={data}/>
<MonumentMaker category={'Museus'} navigate={navigate} data={data}/>
<MonumentMaker category={'Igrejas'} navigate={navigate} data={data}/>
<MonumentMaker category={'Locais de Interesse'} navigate={navigate} data={data}/>
);
}
}
export default TabNavigator({
Convento: {
screen:**first component**
},
Museus: {
screen:**second component**
},
Igrejas: {
screen:**third component**
},
OutrosLocais: {
screen:**forth component**
}
})
It is possible what I want to accomplish?
Thank you for your help!
You can make your TabNavigation as follows
const myTabNavigator = TabNavigator({
Convento: {
screen: { screen: props => <Monument {...props} {...myProps} /> }
},
Museus: {
screen: { screen: props => <Monument {...props} {...myProps} /> }
},
Igrejas: {
screen: { screen: props => <Monument {...props} {...myProps} /> }
},
OutrosLocais: {
screen: { screen: props => <Monument {...props} {...myProps} /> }
}
})
Monument.js
const Monument = ({...props}, {...myProps}) => (
<View>
{...// More stuff}
</View>
)

How to pass redux store in react native between pages?

In react native app I have 2 pages. If I upload a redux store with data on the 2 page, then return to the 1 page - how can I access the store with the uploaded data from the 2 page? So is there a way to access the store with data from all of the pages in react native?
Maybe simoke example or where to read?
Thanks
1page.js
class ScreenHome extends Component{
static navigationOptions = {
title: 'ScreenHome',
};
constructor(props){
super(props)
console.log("PROPS: ",props);
}
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Button
title="Go to load data page"
onPress={() => navigate('New', { name: 'Jane' })}
/>
<Button
title="Get redux data"
onPress={() => {console.log(this.props)}}
/>
</View>
);
}
}
class ScreenRegister extends Component{
static navigationOptions = {
title: 'ScreenRegister',
};
render(){
return <Text>ScreenRegister</Text>
}
}
const MainScreenNavigator = DrawerNavigator({
Recent: {
screen: ScreenHome
},
All: {
screen: ScreenRegister
},
});
export default SimpleApp = StackNavigator({
Home: {
screen: MainScreenNavigator
},
Chat: {
screen: ScreenHome
},
New: {
screen: testScreen
}
});
const mapStateToProps = (state) => {
const {items, isFetching, done} = state.myTestData
return {testScreen:{items, isFetching, done}};
}
const mapDispatchToProps = (dispatch) => {
return {
getNewItems: () => {
dispatch(fetchData());
}
}
}
export default someTest = connect(
mapStateToProps,
mapDispatchToProps
)(SimpleApp)
2page.js
class testScreen extends Component{
static navigationOptions = {
title: 'testScreen.js',
};
_reduxStuff = () => {
this.props.getNewItems();
}
render() {
const { navigate } = this.props.navigation;
const {done, items, isFetching} = this.props.testScreen;
return (
<View>
<Text>Some new screen</Text>
<Button
title="Load Data"
onPress={() => this._reduxStuff()}
/>
</View>
);
}
}
const mapStateToProps = (state) => {
const {items, isFetching, done} = state.myTestData
return {testScreen:{items, isFetching, done}};
}
const mapDispatchToProps = (dispatch) => {
return {
getNewItems: () => {
dispatch(fetchData());
}
}
}
export default FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(testScreen)
There should be a container for each page, a store for data you want to access between pages and actions to changing this store. By using mapStateToProps you can pass this store to the container of the page. You can find good example in here.
On your first container you'll need to make your async calls to fill your store.
You can do a dispatch on your componentWillMount() and populate your store with the received data.