Set navigator parameters from child navigator? - react-native

I have a Stack navigator (RecipesTab) nested in a Tab navigator (NavBar) and I'm trying to hide the Tab Bar on RecipeSite. My current solution is to pass showTabBar up the tree but I'm having trouble setting the navigation parameters for NavBar from RecipesTab. Wondering if it's possible to somehow call this.props.navigation.setParams({...}) from the RecipesTab navigator or pass parameters to NavBar from RecipesTab in another way.
//class RecipeList...
//class IngredientsTab...
class RecipeSite extends Component {
render() {
this.props.navigation.setParams({showTabBar: false});
return;
}
}
const RecipesTab = createStackNavigator(
{
Main: {
screen: RecipeList,
},
Site: {
screen: RecipeSite,
}
},
{
initialRouteName: 'Main',
}
);
export default NavBar = createBottomTabNavigator(
{
Recipe: {
screen: RecipesTab,
navigationOptions: ({ navigation }) => ({
tabBarVisible: navigation.getParam('showTabBar', true)
}),
},
Ingredient: {
screen: IngredientsTab,
}
},
);`
Basically I'm just trying to send data from RecipesTab to NavBar.

Related

How do I create an embedded Stack navigator inside a React Native formSheet modal?

Like so:
I'm running react-navigation v4.
First you have to follow the tutorial on how to set up a react-navigation modal, the one that has a jump animation and doesn't look like the native formSheet. You have to set up a stack navigator with your root navigator as one child and the modal as another:
And it scales, because you can have more than one of these modals as children.
The code for this is the following:
const RootNavigator = createStackNavigator(
{
index: { screen: AppNavigator },
[NC.SCREEN_ROOM_INFO]: { screen: RoomInfoNavigator },
[NC.SCREEN_CHAT_CREATE]: { screen: RoomCreateNavigator },
[NC.SCREEN_CHAT_SEARCH]: { screen: ChatSearchNavigator },
[NC.SCREEN_CHAT_GIF_PICKER]: { screen: GifPickerNavigator }
},
{
mode: 'modal',
headerMode: 'none',
transitionConfig: () => ({
transitionSpec: {
duration: 0
}
}),
transparentCard: true
}
)
Then you need to implement these, from my example, 4 navigators that will be displayed as modals each like so:
// Here you'll specify the screens you'll navigate in this modal, starting from index.
const RoomInfoStack = createStackNavigator({
index: { screen: NavigableRoomInfo },
[NC.SCREEN_ROOM_ROSTER]: { screen: NavigableRoomRoster },
[NC.SCREEN_ROOM_NOTIFICATION_PREFERENCES]: { screen: NavigableRoomNotificationPreferences },
[NC.SCREEN_ROOM_EDIT]: { screen: NavigableRoomEdit }
})
type NavigationComponent<T = any> = {
navigation?: NavigationScreenProp<NavigationState, T>
}
type Props = NavigationComponent
// THIS code is from the react-navigation tutorial on how to make a react-navigation modal:
// https://reactnavigation.org/docs/4.x/custom-navigators/#extending-navigators
class RoomInfoNavigator extends React.Component<Props> {
static router = {
...RoomInfoStack.router,
getStateForAction: (action, lastState) => {
// check for custom actions and return a different navigation state.
return RoomInfoStack.router.getStateForAction(action, lastState)
}
}
constructor(props) {
super(props)
this.onClose = this.onClose.bind(this)
}
onClose() {
this.props.navigation?.goBack()
}
// And here is the trick, you'll render an always open RN formSheet
// and link its dismiss callbacks to the goBack action in react-navigation
// and render your stack as its children, redirecting the navigator var to it in props.
render() {
return (
<Modal
visible={true}
animationType={'slide'}
supportedOrientations={['portrait', 'landscape']}
presentationStyle={'formSheet'}
onRequestClose={() => this.onClose()}
onDismiss={() => this.onClose()}
>
<RoomInfoStack {...this.props} />
</Modal>
)
}
}
export { RoomInfoNavigator }
This export is what our root stack imported before. Then you just need to render the screens, I have a pattern that I do to extract the navigation params to props in case this screen is ever displayed without navigation:
const NavigableRoomInfo = (props: NavigationComponent<RoomInfoProps>) => {
const roomInfo = props.navigation!.state!.params!.roomInfo
const roomInfoFromStore = useRoomFromStore(roomInfo.id)
// Here I update the navigation params so the navigation bar also updates.
useEffect(() => {
props.navigation?.setParams({
roomInfo: roomInfoFromStore
})
}, [roomInfoFromStore])
// You can even specify a different Status bar color in case it's seen through modal view:
return (
<>
<StatusBar barStyle="default" />
<RoomInfo roomInfo={roomInfoFromStore} navigation={props.navigation} />
</>
)
}
Sources:
https://reactnavigation.org/docs/4.x/modal
https://reactnavigation.org/docs/4.x/custom-navigators/#extending-navigators

How to organize navigation structure in React Native?

I'm using react-navigation library. Currently the navigation is organized in this way:
App.js:
const Layout = createRootNavigator(signedIn);
return (
<AppFontLoader>
<Layout />
</AppFontLoader>
);
AppNavigator:
export const createRootNavigator = (signedIn = false) => {
return createSwitchNavigator(
{
SignedIn: {
screen: SignedIn
},
SignedOut: {
screen: SignedOut
}
},
{
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
);
};
AppNavigator:
export const SignedIn = createMaterialBottomTabNavigator(
{
MeetingsScreen: {
...
}
MeetingsScreen:
const MeetingNavigator = createStackNavigator({
MeetingsListScreen: {
screen: MeetingsListScreen,
navigationOptions: {
}
},
AddMeetingForm: {
screen: AddMeetingFormScreen
},
MeetingScreen: {
screen: MeetingScreen
}
}, {initialRouteName: "MeetingsListScreen"});
The error is shown with the current structure:
You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator.
Apparently, I shouldn't nest one navigator into another, but I'm struggling to come up with the right navigation structure.
How to organize the navigation so that I can have more layers of navigation to move between screens?
I've encountered the same issue, so what I ended up doing was just making one navigation. Rootstack. All routes are there. My app.js has only root stack and navigations.

Custom header in nested TabNavigator

I have a fairly complication navigation flow requirement for an app I'm working on.
I have a bottom tab bar, for each tab I'll be having a top tab bar for additional related views.
Which I have working, however on the videos tab in the nested "All" tab, I need to add a search bar to the header, and on the "Favourites" tab I'll be having yet another custom header with an "Edit" button at the top right.
How can I achieve this navigation whilst allowing React Navigation to co-ordinate everything. See images below:
What I don't want to do is disable the header at the MainNavigator level and enable it for particular routes. Or even worse embed the header and the tab bar on individual containers.
routes.js
import {
StackNavigator,
TabNavigator,
DrawerNavigator
} from "react-navigation";
import Feed from "./containers/Feed";
import Auth from "./containers/Auth";
import News from "./containers/News";
import Videos from "./containers/Videos";
import FavouriteVideos from "./containers/FavouriteVideos";
const DashboardNavigator = TabNavigator(
{
Feed: {
screen: Feed
},
News: {
screen: News
}
},
{
tabBarPosition: "top"
}
);
const VideoNavigator = TabNavigator(
{
Videos: {
screen: Videos,
navigationOptions: {
title: "All"
}
},
Favourites: {
screen: FavouriteVideos
}
},
{
tabBarPosition: "top"
}
);
const MainNavigator = TabNavigator(
{
Dashboard: {
screen: DashboardNavigator,
navigationOptions: ({}) => ({
title: "Dashboard"
})
},
Video: {
screen: VideoNavigator,
navigationOptions: ({}) => ({
title: "Videos"
})
}
},
{
swipeEnabled: false,
animationEnabled: false,
tabBarPosition: "bottom"
}
);
const AuthenticatedNavigator = DrawerNavigator({
App: {
screen: MainNavigator
}
});
const RootNavigator = StackNavigator({
LoggedOut: {
screen: Auth
},
Authenticated: {
screen: AuthenticatedNavigator
}
});
export default RootNavigator;
Snack
https://snack.expo.io/H1qeJrLiM
Images
You can use react-navigation addListener function with combination setParams to achieve desired behavior.
You can listen for focus and blur events and then change a parameter. Then in your route config you can look for this parameter and decide what to render for header. I changed your snack to show a working example of what I am suggesting.
Example
const MainNavigator = TabNavigator(
{
Dashboard: {
screen: DashboardNavigator,
navigationOptions: ({}) => ({
title: "Dashboard"
})
},
Video: {
screen: VideoNavigator,
navigationOptions: ({navigation}) => {
let title = 'Videos';
navigation.state.routes.forEach((route) => {
if(route.routeName === 'Videos' && route.params) {
title = route.params.title;
}
});
// I set title here but you can set a custom Header component
return {
tabBarLabel: 'Video',
title
}
}
}
},
{
swipeEnabled: false,
animationEnabled: false,
tabBarPosition: "bottom"
}
);
export default class Videos extends Component {
constructor(props) {
super(props);
this.willFocusSubscription = props.navigation.addListener(
'willFocus',
payload => {
this.props.navigation.setParams({title: 'All Videos'});
}
);
this.willBlurSubscription = props.navigation.addListener(
'willBlur',
payload => {
this.props.navigation.setParams({title: 'Just Videos'});
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
this.willBlurSubscription.remove();
}
render() {
return (
<View>
<Text> Videos </Text>
</View>
);
}
}

How to hide tabBar from a child stack to parent stack navigator?

I have a screen 'About' which doesn't need a tabBar but other siblings to that screen must have tabBar.
the third element in the tab named 'Settings' have a default class in which i used a stack navigator.
export default class Setting extends Component {
constructor(props) {
super(props);
this.state = {
}
}
static navigationOptions = {
header: null,
tabBarVisible:true // if i place false here the tabBar is invisible on all screens in below specified stack.
};
render() {
return (
<SettingStack />
);
}
}
export const SettingStack = StackNavigator({
// screenname : { screen : ImportedClassname }
settingsscreen: { screen: SettingsScreen },
first: { screen: first },
second: { screen: second },
about: { screen: About },
third: { screen: third },
});
How can i hide the tabBar only on 'about' screen .
just remove the default export class and use that "settingstack" in your parent navigation tree. it will work for sure if not please get back...

How to handle click event on tab item of TabNavigator in React Native App using react-navigation?

I am programming React Native App. I am using react-navigation for navigating screens.
I have 2 Stack Navigators, they are stayed in a Tab Navigator. I want handle click event when I press tab item on TabBar to refresh its view.
export const HomeStack = StackNavigator({
Home: {screen: ScreenHome},
Detail: {screen: ScreenDetail}
})
export const UserStack = StackNavigator({
User: {screen: ScreenUser}
})
export const Tabbar = TabNavigator({
HomeTab: {
screen: HomeStack,
},
UserTab: {
screen: UserStack,
}
}, {
tabBarComponent: ({ jumpToIndex, ...props}) => (
<TabBarBottom
{...props}
jumpToIndex={(index) => {
if(props.navigation.state.index === index) {
props.navigation.clickButton(); //----> pass props params (code processes)
}
else {
jumpToIndex(index);
}
}
}
/>
),
tabBarPosition: 'bottom'
});
class TabClass extends Component{
constructor(props){
super(props)
this.clickButton = this.clickButton.bind(this)
}
clickButton(){
console.log('click button')
}
render (){
return (
<Tabbar clickButton={()=> this.clickButton()}/>
)
}
}
I want to pass clickButton() function from TabClass Component to the code which processes event click tab bar. When if(props.navigation.state.index === index), I want it will call clickButton(). I try it but it doesn't work.
Is there any way to solve my matter?
I tried onNavigationStateChange(prevState, currentState).
class TabClass extends Component{
constructor(props){
super(props)
this.clickButton = this.clickButton.bind(this)
}
clickButton(){
console.log('click button')
}
_handleNavagationStateChange(prevState, currentState){
console.log('handle navigation state change')
}
render (){
return (
<Tabbar onNavigationStateChange={(prevState, currentState) => {
this._handleNavagationStateChange(prevState, currentState)
}}
clickButton={()=> this.clickButton()}
/>
)
}
}
However, _handleNavagationStateChange(prevState, currentState) only run when navigation state changes (for examples, if I stay at HomeTab, I click User Tab Item, this function will run; if I stay at HomeTab, I click Home Tab Item, this function will not run).
Is there any way to handle click event on tab item.
according to the newest documentation here, you can use navigationOptions: {navigationOptions: () => doWhatever()} to handle tab bar taps.
From the react-navigation 4.x docs, you can override tabBarOnPress within navigationOptions:
tabBarOnPress: ({ navigation, defaultHandler }) => {
defaultHandler(); // Call the default handler to actually switch tabs
// do extra stuff here
},
Please try the code following when customize the event touch of TabBar:
import { TabBarBottom, TabNavigator, StackNavigator } from 'react-navigation';
export const MainScreenNavigator = TabNavigator({
Home: { screen: HomeViewContainer },
Search: { screen: SearchViewContainer },
}, {
tabBarComponent: ({ jumpToIndex, ...props, navigation }) => (
<TabBarBottom
{...props}
jumpToIndex = { tabIndex => {
const currentIndex = navigation.state.index;
if (tabIndex == 1) {
// do some thing. Call Native Live Stream Record
} else {
jumpToIndex(tabIndex);
}
console.log('Select tab: ', tabIndex);
}}
/>),
tabBarPosition: 'bottom',
});