问题
I am building a mobile application with React Native and I am using React Navigation to build a navigator inside my application. React navigation provided me a good way to handle nested Tab bars inside a drawer which is also inside a Stack Navigator.
The problem is that I need to specify components so that I can provide these into the Tab Bar. Lets say we have to fetch some categories from an API and we do not know how many categories are inside the data. Besides, I could not figure out that even if I try to fetch data at start, the navigator and redux configuration takes place at start which means the application has to know the components in those tab navigators. I could not figure out that even if I fetched the data from the API, how I can create multiple components and while stopping the application configuration.
The code below, just demonstrates how I implemented the tab bar. This code works in index.js because as I mentioned before, the application have to know the components inside the Navigator.
const TabStack = TabNavigator({
Food: { screen: FoodStack},
Drink : { screen: DrinkStack },
HealthCare : { screen: SnackProducts },
Snacks: { screen: SnackStack },
},
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Food',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
Thanks
here the root code
import { AppRegistry } from 'react-native';
import React from 'react';
import { Text, Image, ScrollView, View, List, ListItem, TouchableWithoutFeedback } from 'react-native';
import { Icon, Avatar } from 'react-native-elements';
import { Provider, connect } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import { createStore, applyMiddleware, combineReducers } from 'redux';
import {
addNavigationHelpers, StackNavigator,
DrawerNavigator,
DrawerItems,
TabNavigator,
TabView,
TabBarTop,
NavigationActions
} from 'react-navigation';
// importing starting screen
import StartingContainer from './src/containers/StartingScreen/StartingContainer';
// Menu Containers
import MenuCredentials from './src/containers/MenuCredentials';
// Containers
import LoginContainer from './src/containers/LoginContainer';
import PhoneNumberValidation from './src/containers/SubLoginContainers/PhoneNumberValidation';
import MainOrderContainer from './src/containers/OrderContainers/MainOrderContainer';
import MainCartContainer from './src/containers/CartContainers/MainCartContainer';
// Components
// Login Components
import SMSLogin from './src/containers/SubLoginContainers/SMSLogin';
// Profil Components
import Profil from './src/components/ProfileComponents/Profile';
import AdressComponent from './src/components/ProfileComponents/AdressComponent';
import SettingsComponent from './src/components/ProfileComponents/SettingsComponent';
import creditCardComponent from './src/components/ProfileComponents/creditCardComponent';
// Reducers
import initialReducer from './src/reducers/initialReducer';
import cartReducer from './src/reducers/cartReducer';
import starterReducer from './src/reducers/starterReducer';
// import tab bar containers
import FoodProducts from './src/containers/TabBarContainers/FoodProducts';
import HealthProducts from './src/containers/TabBarContainers/HealthProducts';
import SnackProducts from './src/containers/TabBarContainers/SnackProducts';
// Building Navigation
import MenuItem from './src/containers/MenuItemContainer/MenuItem';
import CustomTabItems from './src/containers/CustomTabItems';
import CustomSubTabItems from './src/containers/CustomSubTabItems';
import DrawerButton from './src/containers/DrawerButton';
// Tab Bar Navigation
const ChocolateStack = TabNavigator({
Tadelle: { screen: MenuItem},
Milka: { screen: MenuItem},
},
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Tadelle',
swipeEnabled: true,
tabBarOptions: {
scrollEnabled: true
},
})
const SnackStack = TabNavigator({
Çikolatalar: { screen: MenuItem},
Gofretler: { screen: MenuItem},
Krakerler: { screen: MenuItem},
Bisküviler: { screen: MenuItem},
Kuruyemişler: { screen: MenuItem},
},
{
tabBarComponent : props => <CustomSubTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Çikolatalar',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
const DrinkStack = TabNavigator({
'Gazlı İçecekler': { screen: MenuItem},
'Soğuk Çaylar': { screen: MenuItem},
'Alkol': { screen: MenuItem},
'Süt Ürünleri': { screen: MenuItem},
'Spor İçecekleri': { screen: MenuItem},
},
{
tabBarComponent : props => <CustomSubTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Alkol',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
const FoodStack = TabNavigator({
Sandviç : { screen: MenuItem},
Çorba: { screen: MenuItem},
},
{
tabBarComponent : props => <CustomSubTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Sandviç',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
const TabStack = TabNavigator({
Food: { screen: FoodStack},
Drink : { screen: DrinkStack },
Health : { screen: SnackProducts },
Snacks: { screen: SnackStack },
},
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Food',
swipeEnabled: true,
tabBarOptions : {
tabStyle : {
width : 250
},
scrollEnabled : true
}
})
// cart navigation will be drawernavigator and drawerItems will be custom !!
const CartNavigation = StackNavigator({
Cart: {
screen: MainCartContainer,
}
},
{
headerMode: 'float',
navigationOptions: ({ navigation }) => ({
title: 'Sepet',
headerLeft: <Icon
name='arrow-back'
color='#517fa4'
onPress={() => navigation.navigate('drawerStack')}
/>,
headerRight:
<Icon
name='payment'
color='#517fa4'
onPress={() => navigation.navigate('drawerStack')}
/>
})
}
)
const DrawerStack = DrawerNavigator({
Sipariş: { screen: TabStack },
Profil: {
screen: Profil ,
navigationOptions : ({ navigation }) => ({
title : 'Profilim',
})
},
Adreslerim: {
screen: AdressComponent,
navigationOptions: ({ navigation }) => ({
title: 'Teslimat Adreslerim'
})
},
Ayarlar: { screen: SettingsComponent }
},
{
drawerPosition: 'left',
headerMode : 'none',
navigationOptions: ({ navigation }) => ({
headerStyle: { backgroundColor: '#87CEFA' },
headerRight: <Icon
name='shopping-cart'
color='#517fa4'
onPress={() => navigation.navigate('cartStack')}
/>,
}),
contentOptions: {
inactiveTintColor: 'white',
activeTintColor: 'purple',
style: {
marginTop: 80,
marginLeft: 25,
}
},
contentComponent: props => <MenuCredentials {...props} />
})
const DrawerNavigation = StackNavigator({
DrawerStack: {
screen: DrawerStack
}},
{
style : {
leftDrawerWidth : 40
},
index : 0,
navigationOptions : ({ navigation }) => ({
headerStyle: { backgroundColor: '#87CEFA' },
gesturesEnabled : false,
headerRight : <Icon
name='shopping-cart'
color='#517fa4'
onPress={() => navigation.navigate('cartStack')}
/>,
headerLeft: <Icon
name='menu'
color='#517fa4'
onPress={() => {
console.log(navigation.state.routes[0]);
navigation.navigate({
key : null,
index : 0,
action : [
navigation.navigate('DrawerToggle')
]
})
}}
/>
}),
initialRouteParams : {
name : 'Welcome'
}
}
)
const LoginStack = StackNavigator({
Login: {
screen: LoginContainer,
navigationOptions: ({ navigation }) => ({
title: ' GİZLİ UYGULAMA ! '
})
},
Ss: {
screen: SMSLogin,
navigationOptions: ({ navigation }) => ({
title: ' SMS ONAYI '
})
},
PhoneNumberValidation: {
screen: PhoneNumberValidation,
navigationOptions: ({ navigation }) => ({
title: 'Kaydolma'
})
},
},{
headerMode : 'none',
initialRouteName : 'Login'
})
// IMPORTANT NOTE ***!!!
// CARRY drawerStack to the PrimaryNavigator !!
// CHANGE LoginContainer so that it will navigate to the drawerStack
// NOT FROM ACTION BUT FROM COMPONENT INSIDE COMPONENTWILLUPDATE
// BY CHANGING isAuth variable in initialReducer !!
const PrimaryNavigator = StackNavigator({
loginStack: {
screen: LoginStack
},
cartStack: {
screen: CartNavigation
},
drawerStack: {
screen: DrawerNavigation
},
starter : {
screen : StartingContainer
}
},
{
headerMode: 'none',
title: 'Main',
initialRouteName : 'starter'
}
)
const navReducer = (state, action) => {
const nextState = PrimaryNavigator.router.getStateForAction(action, state);
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
};
// combining Reducers
const AppReducer = combineReducers({
initialR: initialReducer,
cartR: cartReducer,
starterR : starterReducer,
nav: navReducer
})
// Creating redux store
const store = createStore(
AppReducer,
applyMiddleware(thunkMiddleware)
)
// Navigation initilizator to App
class App extends React.Component {
render() {
return (
<PrimaryNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
)
}
}
const mapStateToProps = (state) => ({
nav: state.nav
})
const AppWithNavigationState = connect(mapStateToProps)(App);
class brilliantApp extends React.Component{
render(){
return(
<Provider store={store}>
< AppWithNavigationState />
</Provider>
)
}
}
AppRegistry.registerComponent('brilliantApp', () => brilliantApp);
回答1:
Your TabStack file:
const CATEGORIES = {
"Food": { screen: FoodStack },
// ...
}
export default (screenNames) => {
const screens = screenNames.reduce((total, name) => ({...total, [name]: CATEGORIES[name]}), {})
const TabStack = TabNavigator(screens,
{
tabBarComponent : props => <CustomTabItems props={props}/>,
tabBarPosition: 'top',
animationEnabled : true,
initialRouteName : 'Food',
swipeEnabled: true,
tabBarOptions : {
scrollEnabled : true
}
})
return TabStack
}
Your Root file:
import getTabStack from './TabStack'
class Root extends Component {
state = {
categoriesNames: null
}
componentWillMount() {
// assuming result is ["Food", "Drink", ... ]
Api.fetchCategories().then((result) => {
this.setState({ categoriesNames: result })
})
}
render() {
const { categoriesNames } = this.state
if (!categoriesNames) {
return <SplashScreen />
}
const TabStack = getTabStack(categoriesNames)
return (
<Provider store={store} >
<TabStack />
</Provider>
);
}
}
回答2:
Here I would like to post a method for creating tab bar according to the data we fetched from some API etc programmatically.
Here we fetch the data from API in this example, this code from the top level component :
renderShopTab() {
const { client } = this.props;
try {
const { categories } = client.readQuery({
query: gql`
{
categories{
id
name
products{
id
name
price
quantity
}
}
}`
})
console.log("Categories :" + categories);
return (
<ShopCreator categories={categories} />
)
} catch (error) {
console.log("Error occured creating the categories due to the : " + error);
return (
<View>
<Text>
Loading...
</Text>
</View>
)
}
}
This code snippet is from creator of the tab bar dynamically :
export const ShopCreator = ({ categories }) => {
// This script will create a TabNavigator for categories and StackNavigators for each member of the categories !
let categoryStack = {};
let routes = {};
categories.forEach((category) => {
if (category.products.length > 0) {
const { catname } = category.name;
if (category.name != undefined) {
routes[category.name] = {
screen: StackNavigator({
'isim': {
screen: ProductPage
}
},{
headerMode : 'none',
initialRouteParams : {
categoryName : category.name,
products : category.products
}
})
}
}
} else {
console.log("This category has no products !");
}
})
console.log("OHA : " + JSON.stringify(routes));
const ShopCatTabNav = TabNavigator(routes, {
tabBarPosition: 'top',
tabBarComponent: props => <TabMenuItems props={props} />
})
return <ShopCatTabNav />
}
As last , I will show you customized tab navigation bar I built :
const TabMenuItems = ({props}) => {
const { activeTintColor, tab, tabbar, tabText, inactiveTintColor } = styles;
const { index } = props.navigation.state;
return(
<View>
<ScrollView contentContainerStyle={{ flex : 1 }} horizontal showsHorizontalScrollIndicator={false} style={{backgroundColor : '#FFAEB9'}}>
{
props.navigation.state.routes.length ? (
props.navigation.state.routes.map((route,number)=>{
const focused = ( index === number ) ? '#1874CD' : '#FF6A6A';
const tintColor = focused ? activeTintColor : inactiveTintColor;
return (
<TouchableWithoutFeedback
key={route.key}
onPress={() => {
props.jumpToIndex(number)
}}
delayPressIn={0}
>
<View style={{marginLeft : 20, marginTop : height / 40, shadowOpacity : 25, alignSelf : 'flex-start' }}>
<Text style={{borderRadius : 5, fontWeight : 'bold', borderWidth :2, paddingTop : 5,color : 'white', height : height/18, width : width/5,textAlign : 'center', backgroundColor : focused, borderStyle: 'dashed',borderColor : '#CD2626'}}>
{props.getLabel({route, number})}
</Text>
</View>
</TouchableWithoutFeedback>
)
})
) : null
}
</ScrollView>
</View>
)
}
export default TabMenuItems;
回答3:
It's been a while this q was posted but if there are still some people looking at this. I'd rather using react-native-navigation
instead this library.
来源:https://stackoverflow.com/questions/47253525/react-native-creating-navigator-dynamically-with-react-navigation