I had the same problem, I needed to add a custom component, not related with a screen, to the tab navigator and everything I tried it failed. In my case I was trying with createMaterialTopTabNavigator.
Documentation of React Navigation 5 is a little rough and there isn't a lot of examples of React Navigation 5, but after many attempts I could create a custom component and style it myself so I can mix the tabs created by the routes and the custom buttons embedded in the tab navigator.
import * as React from 'react';
import { View } from 'react-native'
import {
NavigationHelpersContext,
useNavigationBuilder,
TabRouter,
TabActions,
createNavigatorFactory,
} from '@react-navigation/native';
import styled from 'styled-components'
import Colors from '../constants/Colors';
const customTabNavigator = ({
initialRouteName,
children,
screenOptions,
tabContainerStyle,
contentStyle,
leftIcon,
rightIcon
}) => {
const { state, navigation, descriptors } = useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});
return (
<NavigationHelpersContext.Provider value={navigation}>
<OuterWrapper style={tabContainerStyle}>
{ leftIcon }
<TabWrapper>
{state.routes.map((route, i) => {
return (
<Tab
key={route.key}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
}}
style={descriptors[route.key].options.tabStyle}
>
{ descriptors[route.key].options.label ?? <Label active={state.index === i}>{descriptors[route.key].options.title || route.name}</Label> }
</Tab>
)
})}
</TabWrapper>
{ rightIcon }
</OuterWrapper>
<View style={[{ flex: 1 }, contentStyle]}>
{descriptors[state.routes[state.index].key].render()}
</View>
</NavigationHelpersContext.Provider>
);
}
const OuterWrapper = styled.View`
height: 55px;
flex-direction: row;
justify-content: space-between;
background-color: ${Colors.grey1};
`
const TabWrapper = styled.View`
flex: 1;
flex-direction: row;
justify-content: space-evenly;
`
const Tab = styled.TouchableOpacity`
padding: 0 24px;
justify-content: center;
height: 100%;
`
const Label = styled.Text`
font-family: Futura-Medium;
font-size: 26px;
color: ${({ active }) => active ? Colors.grey6 : Colors.grey3};
`
export default createNavigatorFactory(customTabNavigator)
import customTabNavigator from './customTabNavigator'
import * as React from 'react';
import { View, Image } from 'react-native'
import {
ProjectsScreen,
RenderScreen,
EventsScreen,
CameraScreen
} from '../screens';
import Colors from '../constants/Colors'
import logo from '../assets/images/icon.png'
import { Ionicons } from '@expo/vector-icons';
import { TouchableOpacity } from 'react-native-gesture-handler';
const TopTab = customTabNavigator();
const INITIAL_ROUTE_NAME = 'Home';
export default function MainNavigator({ navigation, route }) {
navigation.setOptions({ headerTitle: getHeaderTitle(route) });
return (
<TopTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
leftIcon={(
<TouchableOpacity style={{ height: "100%", justifyContent: "center" }} onPress={() => alert("Whatever")}>
<Image source={logo} style={{ resizeMode: "center", width: 70, height: 40 }} />
</TouchableOpacity>
)}
>
<TopTab.Screen
name="Home"
component={ProjectsScreen}
options={{
title: 'Proyectos',
}}
/>
<TopTab.Screen
name="Preview"
component={EventsScreen}
options={{
title: 'Eventos',
}}
/>
<TopTab.Screen
name="Render"
component={RenderScreen}
options={{
title: 'Mi cuenta',
}}
/>
<TopTab.Screen
name="Camera"
component={CameraScreen}
options={{
title: "Camera",
label: (
<View style={{ width: 36, height: 32, backgroundColor: Colors.grey3, borderRadius: 3, alignItems: "center", justifyContent: "center" }}>
<Ionicons name="md-camera" style={{ color: Colors.grey5 }} size={25} />
</View>
),
tabStyle: { flexDirection: "row", alignItems: "center", justifyContent: "flex-end", flex: 1 }
}}
/>
</TopTab.Navigator>
);
}
function getHeaderTitle(route) {
const routeName = route.state?.routes[route.state.index]?.name ?? INITIAL_ROUTE_NAME;
switch (routeName) {
case 'Home':
return 'Montar vídeo';
case 'Preview':
return 'Previsualizar vídeo';
case 'Render':
return 'Renderizar';
case 'Gallery':
return 'Galería'
case 'Camera':
return 'Camera'
}
}
To the example of https://reactnavigation.org/docs/custom-navigators I added a different styling and two new props, leftIcon and rightIcon. This props receive a component for render it to the corresponding side of the tab wrapper. And this components can be TouchableWhatevers with a custom onPress not related with a screen :P
I hope it helps, I almost throw myself through the window until I made it work, haha