Expo Ref usage and Navigation Container Conflicting - react-native

I am using Expo Camera to take some user pictures for a profile form. What is happening is when I try to set camera's ref to a state it crashes and throw the following error.
Couldn't find a navigation context. Have you wrapped your app with
'NavigationContainer'? See
https://reactnavigation.org/docs/getting-started for setup
instructions.
Everything is inside a Navigation Container and I really can't figure out what is happening.
Every time I comment ref={(ref) => console.tron.log(ref)} from Expo Camera component everything works fine, but when I uncomment the ref line I get the error.
I am stuck here since last week and nothing on internet about this problem...
Thanks in advance =)
App.js
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { AuthProvider } from './src/context/authContext';
import Routes from './src/routes';
import Theme from './src/theme';
import './src/config/reactotron.config';
export default function App() {
return (
<Theme>
<AuthProvider>
<NavigationContainer>
<Routes />
</NavigationContainer>
</AuthProvider>
</Theme>
);
}
routes.js
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
import arrowLeft from '../../assets/img/arrow-left-header.png';
import SignUp from '../pages/auth/SignUp';
import Profile from '../pages/auth/Profile';
import Login from '../pages/auth/Login';
import Habits from '../pages/auth/Habits';
import AddChildren from '../pages/auth/AddChildren';
import ChildProfile from '../pages/auth/ChildProfile';
import RegisterFinish from '../pages/auth/RegisterFinish';
import { CameraView } from '../components';
const AuthStack = createStackNavigator();
const theme = {
color: {
brandPrimary: '#16B4A1',
white: '#fff',
},
};
const AuthRoutes = () => {
return (
<AuthStack.Navigator
screenOptions={{
headerTintColor: theme.color.white,
headerStyle: {
backgroundColor: theme.color.brandPrimary,
},
cardStyle: {
backgroundColor: theme.color.white,
},
headerBackTitle: 'Voltar',
}}
>
<AuthStack.Screen
name='Profile'
component={Profile}
options={{
headerLeft: null,
}}
/>
<AuthStack.Screen
name='Login'
component={Login}
options={{ headerShown: false }}
/>
<AuthStack.Screen
name='SignUp'
component={SignUp}
options={{ headerShown: false }}
/>
<AuthStack.Screen
name='RegisterFinish'
component={RegisterFinish}
options={{
headerLeft: null,
headerShown: false,
}}
/>
<AuthStack.Screen
name='TakeAPicture'
component={CameraView}
options={{
headerLeft: null,
headerShown: false,
}}
/>
<AuthStack.Screen name='Habits' component={Habits} />
<AuthStack.Screen name='AddChildren' component={AddChildren} />
<AuthStack.Screen name='ChildProfile' component={ChildProfile} />
</AuthStack.Navigator>
);
};
export default AuthRoutes;
CameraView.js
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import { Camera } from 'expo-camera';
import { FontAwesome5 } from '#expo/vector-icons';
import { CameraButton, CameraButtonRing } from './styles';
const CameraView = ({ route, navigation }) => {
const cameraRef = useRef()
// CAMERA SETTINGS
const [hasPermission, setHasPermission] = useState(null);
const [type, setType] = useState(Camera.Constants.Type.front);
useEffect(() => {
console.tron.log('rolou')
async function handleCameraPermission() {
const { status } = await Camera.requestPermissionsAsync();
setHasPermission(status === 'granted');
}
handleCameraPermission();
}, []);
const handleTakePictureButton = async () => {
if (!cameraRef) {
console.tron.log('Não tem cam ref')
return
};
let photo = await cameraRef.takePictureAsync();
console.tron.log(photo)
handlePicture(photo)
}
return (
<View style={{ flex: 1 }}>
<Camera
style={{ flex: 1 }}
type={type}
ref={(ref) => console.tron.log(ref)}
>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
border: '1px solid red '
}}
>
<TouchableOpacity
style={{
// flex: ,
// alignSelf: 'flex-end',
// alignItems: 'center',
position: 'absolute',
bottom: 40,
right: 32,
}}
onPress={() => {
setType(
type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back
);
}}
>
<FontAwesome5 name='sync-alt' size={32} color='#fff' />
</TouchableOpacity>
<TouchableOpacity
style={{
// flex: 0.1,
alignSelf: 'flex-end',
height: 56,
width: 56,
backgroundColor: 'transparent',
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 24,
border: '1px solid red'
}}
onPress={handleTakePictureButton}
>
<View style={{position: 'relative'}}>
<CameraButton />
<CameraButtonRing />
</View>
</TouchableOpacity>
</View>
</Camera>
</View>
);
};
export default CameraView;

For anyone stumbling on this, I really don't know the reason behind, but what was crashing my app with the exact same error, was trying to log to Reactotron. It was the only thing in common with the code on the question, and after removing that, the application stopped crashing and worked just fine.
Now... if anyone discovers the reason, I would really like to know as well.

Related

React-Native Paper Drawer.Navigator is invisible when Appbar has Appbar.Actions

I have a fairly simple React Native app that I am using to showcase a custom component library. I recently upgraded the app from React Native version 0.67.2 to 0.70.6. React Navigation is still on version 5.x (latest is 6.x). My issue is that when I attempt to open my navigation drawer, I see a shadow over my home screen, but the drawer does not appear. The ellipsis menu also fails to appear.
Here is the home screen on app load:
Here is the home screen when I tap the hamburger menu:
I have narrowed down the problem to my custom header component. When I comment out the Actions in the header, including the children, the drawer works.
Here is the home page:
HomeStack.tsx:
import { CustomHeader } from '#custom-components/Header';
import { createStackNavigator } from '#react-navigation/stack';
import React from 'react';
import { Appbar, Menu, Divider, useTheme } from 'react-native-paper';
import { HomeScreen } from '../screens';
const Stack = createStackNavigator();
const HomeStack = (): JSX.Element => {
const theme: ReactNativePaper.Theme = useTheme();
const [isMenuVisible, setMenuVisible] = React.useState(false);
const openMenu = () => setMenuVisible(true);
const closeMenu = () => setMenuVisible(false);
return (
<Stack.Navigator
headerMode="float"
screenOptions={{
header: () => (
<CustomHeader title="Home">
<Menu
visible={isMenuVisible}
onDismiss={closeMenu}
anchor={
<Appbar.Action
icon="dots-vertical"
testID="header-action-1"
touchSoundDisabled={false}
onPress={openMenu}
color={theme.colors.text}
/>
}
>
<Menu.Item onPress={() => {}} title="Item 1" />
<Menu.Item onPress={() => {}} title="Item 2" />
<Menu.Item onPress={() => {}} title="Item 3" />
</Menu>
</CustomHeader>
),
}}
>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
);
};
export default HomeStack;
And the header:
CustomHeader.tsx
import { DrawerActions, useNavigation } from '#react-navigation/native';
import React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import { Appbar, useTheme } from 'react-native-paper';
import { common } from '../theme/colors';
export type CustomHeaderProps = {
title: string;
subtitle?: string;
scaleMenuIcon?: number;
headerHeight?: number;
titleSize?: number;
subtitleSize?: number;
children?: React.ReactNode;
backgroundColor?: string;
color?: string;
};
export const CustomHeader = ({
title,
subtitle,
scaleMenuIcon,
titleSize = 20,
subtitleSize = 15,
headerHeight,
children,
backgroundColor = common.offWhite,
color = common.black,
}: CustomHeaderProps): JSX.Element => {
const navigation = useNavigation();
const theme = useTheme();
const openMenu = () => {
navigation.dispatch(DrawerActions.openDrawer());
};
return (
<Appbar.Header style={
backgroundColor: theme.dark ? undefined : backgroundColor,
height: headerHeight
}>
<Appbar.Action
icon="menu"
testID="header-button"
style={styles.menuIcon}
onPress={openMenu}
touchSoundDisabled={true}
size={scaleMenuIcon}
color={theme.dark ? undefined : color}
/>
<Appbar.Content
testID="appbar-content"
color={theme.dark ? undefined : color}
style={styles.headerText}
title={title}
titleStyle={{ fontSize: titleSize }}
subtitle={subtitle}
subtitleStyle={{ fontSize: subtitleSize }}
/>
// If I comment out this View, the drawer appears.
<View style={styles.headerImageText}>
{children && (
<View testID="appbar-actions" style={styles.children}>
{children}
</View>
)}
</View>
</Appbar.Header>
);
};
const styles = StyleSheet.create({
children: {
flexDirection: 'row',
justifyContent: 'flex-end',
paddingRight: 30,
...Platform.select({
ios: {
left: -10,
},
android: {
left: 0,
},
}),
},
headerImageText: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'flex-start',
paddingLeft: 10,
},
headerText: {
flexGrow: 6,
flexShrink: 1,
fontSize: 20,
fontWeight: 'bold',
justifyContent: 'center',
left: -10,
letterSpacing: 1,
},
menuIcon: {
left: 10,
},
});

React native (React navigation)- instance of same screen in differents tabs. Navigate from drawer

I have an issue with react navigation.
I would like to have the same behavior as the linkedIn app has.
I mean, in that app, you can open the settings page from the drawer in differents tabs. You can open it from the first tab, second one... and so on. and at the end you get multiple instances.
I am not able to reproduce this behavior
My navigation is:
One drawer with one drawer screen (one Tab navigator inside). 3 tabs screens inside that Tab navigator. Each one contains a stack. I have set the same screens in that screen. For example I want to share the Profile, so I have one profile screen in each tab.
In the customDrawer I navigate to screens name, but here is the problem. React navigation does not know what stack should call before calling the right screen.
And I cannot find a way to know the current mounted stack so I cannot set it dinamically in order to do inside the custom drawer:
onPress={() =>
props.navigation.navigate(Routes.student.home.STACK, { screen: Routes.student.SETTINGS })
}
Thanks!
App.tsx
/* eslint-disable react-native/no-inline-styles */
import 'react-native-gesture-handler';
import React from 'react';
import { Text } from 'react-native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import {
createDrawerNavigator,
DrawerContentScrollView,
DrawerItem,
} from '#react-navigation/drawer';
import { NavigationContainer } from '#react-navigation/native';
import { QueryClient, QueryClientProvider } from '#tanstack/react-query';
import { StatusBar } from 'expo-status-bar';
import HomeStackScreen from '#pages/Home';
import { Routes } from '#src/constants/routes';
import { PracticalStackScreen } from '#src/pages/Practical/Practical';
import { TheoryStackScreen } from '#src/pages/Theory/Theory';
// Create a client
const queryClient = new QueryClient();
const Tab = createBottomTabNavigator();
const Drawer = createDrawerNavigator();
const TabNavigator = () => {
return (
<Tab.Navigator
screenOptions={{ headerShown: false }}
initialRouteName={Routes.student.home.STACK}
>
<Tab.Screen
name={Routes.student.theory.STACK}
component={TheoryStackScreen}
options={{ title: 'TEÓRICO' }}
/>
<Tab.Screen
name={Routes.student.home.STACK}
component={HomeStackScreen}
options={{ title: '' }}
/>
<Tab.Screen
name={Routes.student.practical.STACK}
component={PracticalStackScreen}
options={{ title: 'PRÁCTICO' }}
/>
</Tab.Navigator>
);
};
function CustomDrawerContent(props: any) {
console.log('props ', props);
return (
<DrawerContentScrollView {...props}>
<Text>Mi cuenta</Text>
<DrawerItem
label='Configuración'
onPress={() => props.navigation.navigate(Routes.student.SETTINGS)}
/>
<DrawerItem
label='Métodos de pago'
onPress={() => props.navigation.navigate(Routes.student.PAYMENT)}
/>
<Text>Social</Text>
<DrawerItem
label='Tiktok'
onPress={() => props.navigation.navigate(Routes.student.PROFILE)}
/>
<Text>Ayuda</Text>
<DrawerItem
label='Preguntas frecuentes'
onPress={() => props.navigation.navigate(Routes.student.FAQ)}
/>
<DrawerItem
label='Atención al alumno'
onPress={() => props.navigation.navigate(Routes.student.STUDENT_SUPPORT)}
/>
</DrawerContentScrollView>
);
}
const DrawerNavigation = () => {
return (
<Drawer.Navigator
useLegacyImplementation={true}
drawerContent={(props) => <CustomDrawerContent {...props} />}
initialRouteName={Routes.student.home.STACK}
>
<Drawer.Screen name={Routes.MAIN_TAB} component={TabNavigator} />
</Drawer.Navigator>
);
};
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<StatusBar />
<NavigationContainer>
<DrawerNavigation />
</NavigationContainer>
</QueryClientProvider>
);
}
Home.tsx
/* eslint-disable react-native/no-color-literals */
import React from 'react';
import { StyleSheet, Text, View, Pressable } from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { Routes } from '#src/constants/routes';
import Faq from '../Faq';
import Payment from '../Payment';
import Profile from '../Profile';
import Settings from '../Settings';
import StudentSupport from '../StudentSupport';
const HomeStack = createNativeStackNavigator();
export const HomeStackScreen = (): JSX.Element => {
return (
<HomeStack.Navigator
screenOptions={{ headerStyle: { backgroundColor: 'red' }, headerShown: false }}
>
<HomeStack.Screen name={Routes.student.home.MAIN} component={Home} />
<HomeStack.Screen name={Routes.student.PROFILE} component={Profile} />
<HomeStack.Screen name={Routes.student.SETTINGS} component={Settings} />
<HomeStack.Screen name={Routes.student.PAYMENT} component={Payment} />
<HomeStack.Screen name={Routes.student.FAQ} component={Faq} />
<HomeStack.Screen name={Routes.student.STUDENT_SUPPORT} component={StudentSupport} />
</HomeStack.Navigator>
);
};
export const Home = ({ navigation }: any): JSX.Element => (
<View style={styles.container}>
<Text>Home Screen</Text>
<Pressable style={styles.button} onPress={() => navigation.navigate(Routes.student.PROFILE)}>
<Text style={styles.text}>Perfil</Text>
</Pressable>
</View>
);
const backgroundColor = '#fff';
const styles = StyleSheet.create({
button: {
alignItems: 'center',
backgroundColor: 'grey',
borderRadius: 4,
elevation: 3,
justifyContent: 'center',
paddingHorizontal: 32,
paddingVertical: 12,
},
container: {
alignItems: 'center',
backgroundColor,
flex: 1,
justifyContent: 'center',
},
text: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
letterSpacing: 0.25,
lineHeight: 21,
},
});
Theroy.tsx
import React from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { Routes } from '#src/constants/routes';
import Faq from '../Faq';
import Payment from '../Payment';
import Profile from '../Profile';
import Settings from '../Settings';
import StudentSupport from '../StudentSupport';
const TheoryStack = createNativeStackNavigator();
export const TheoryStackScreen = (): JSX.Element => {
return (
<TheoryStack.Navigator
screenOptions={{ headerStyle: { backgroundColor: 'red' }, headerShown: false }}
>
<TheoryStack.Screen name={Routes.student.theory.MAIN} component={Theory} />
<TheoryStack.Screen name={Routes.student.PROFILE} component={Profile} />
<TheoryStack.Screen name={Routes.student.SETTINGS} component={Settings} />
<TheoryStack.Screen name={Routes.student.PAYMENT} component={Payment} />
<TheoryStack.Screen name={Routes.student.FAQ} component={Faq} />
<TheoryStack.Screen name={Routes.student.STUDENT_SUPPORT} component={StudentSupport} />
</TheoryStack.Navigator>
);
};
export function Theory({ navigation }: any): JSX.Element {
return (
<View style={styles.container}>
<Text>Theory Screen</Text>
<Button title='Go to Home' onPress={() => navigation.navigate(Routes.student.home.STACK)} />
<Button
title='Go to Practical'
onPress={() => navigation.navigate(Routes.student.practical.STACK)}
/>
<Button
title='Go to Profile'
onPress={() =>
navigation.navigate(Routes.student.home.STACK, { screen: Routes.student.PROFILE })
}
/>
</View>
);
}
const backgroundColor = '#fff';
const styles = StyleSheet.create({
container: {
alignItems: 'center',
backgroundColor,
flex: 1,
justifyContent: 'center',
},
});
Practical.tsx
import React from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { Routes } from '#src/constants/routes';
import Faq from '../Faq';
import Payment from '../Payment';
import Profile from '../Profile';
import Settings from '../Settings';
import StudentSupport from '../StudentSupport';
const PracticalStack = createNativeStackNavigator();
export const PracticalStackScreen = (): JSX.Element => {
return (
<PracticalStack.Navigator
screenOptions={{ headerStyle: { backgroundColor: 'red' }, headerShown: false }}
>
<PracticalStack.Screen name={Routes.student.practical.MAIN} component={Practical} />
<PracticalStack.Screen name={Routes.student.PROFILE} component={Profile} />
<PracticalStack.Screen name={Routes.student.SETTINGS} component={Settings} />
<PracticalStack.Screen name={Routes.student.PAYMENT} component={Payment} />
<PracticalStack.Screen name={Routes.student.FAQ} component={Faq} />
<PracticalStack.Screen name={Routes.student.STUDENT_SUPPORT} component={StudentSupport} />
</PracticalStack.Navigator>
);
};
export function Practical({ navigation }: any): JSX.Element {
return (
<View style={styles.container}>
<Text>Practical Screen</Text>
<Button title='Go to Home' onPress={() => navigation.navigate(Routes.student.home.STACK)} />
<Button
title='Go to Theory'
onPress={() => navigation.navigate(Routes.student.theory.STACK)}
/>
<Button
title='Go to Profile'
onPress={() =>
navigation.navigate(Routes.student.home.STACK, { screen: Routes.student.PROFILE })
}
/>
</View>
);
}
const backgroundColor = '#fff';
const styles = StyleSheet.create({
container: {
alignItems: 'center',
backgroundColor,
flex: 1,
justifyContent: 'center',
},
});
Try renaming your Settings screen component differently for each stack navigator. So something like
<PracticalStack.Screen name="Practical/Settings" component={Settings} />
<HomeStack.Screen name="Home/Settings" component={Settings} />
Then you could navigate to the appropriate screen from your drawer based on the tab in focus.
I think I found a possible solution to my question. I have changed the CustomDrawerContent.
function CustomDrawerContent(props: any) {
const drawerState = props.state.routes[0]?.state;
const routeIndex = drawerState?.index;
const focusedTab = drawerState?.routes?.[routeIndex]?.name || Routes.student.home.STACK;
return (
<DrawerContentScrollView {...props}>
<Text>Mi cuenta</Text>
<DrawerItem
label='Configuración'
onPress={() => props.navigation.navigate(focusedTab, { screen: Routes.student.SETTINGS })}
/>
<DrawerItem
label='Métodos de pago'
onPress={() => props.navigation.navigate(focusedTab, { screen: Routes.student.PAYMENT })}
/>
<Text>Social</Text>
<DrawerItem
label='Tiktok'
onPress={() => props.navigation.navigate(focusedTab, { screen: Routes.student.PROFILE })}
/>
<Text>Ayuda</Text>
<DrawerItem
label='Preguntas frecuentes'
onPress={() => props.navigation.navigate(focusedTab, { screen: Routes.student.FAQ })}
/>
<DrawerItem
label='Atención al alumno'
onPress={() =>
props.navigation.navigate(focusedTab, { screen: Routes.student.STUDENT_SUPPORT })
}
/>
</DrawerContentScrollView>
);
}

Navigate to other StackNavigator screen when press button on navbar

I'm pretty new to react and this is my first app.
I have a stack navigator with 2 screens by now: MainMenu and Profile. While the user is in MainMenu, a button on top right corner is shown and I need to redirect the user to the Profile screen when this button is pressed. I need something like this.props.navigation.navigate('Profile') but this does not work, because this, props and navigation are not defined.
My thinks are that I cannot redirect to Profile from this stack navbar, cause Profile is still defined yet, but I don't know another way to do it.
// mainStack.js
import React from 'react';
import { View, Text, TouchableOpacity, Image } from 'react-native';
import { createStackNavigator } from '#react-navigation/stack';
import MainMenu from '../../screens/home/mainMenu';
import Profile from '../../containers/profileContainer';
import Icon from 'react-native-vector-icons/FontAwesome';
import { useSelector } from 'react-redux';
const MainStack = () => {
const Stack = createStackNavigator();
const isAdmin = (useSelector(state => state.auth.user.role) === 'admin');
function renderUserMenu() {
return (
<TouchableOpacity style={{ marginRight: 20 }} onPress={() => console.log("HERE I NEED TO REDIRECT TO THE SCREEN PROFILE") } >
<Icon style={{ color: 'white' }} name='user-circle-o' size={30} />
</TouchableOpacity>
)
}
function LogoTitle() {
return (
<Image
style={{ width: 150, height: 50 }}
source={require('../../assets/logo-with-slogan.png')}
/>
);
}
function renderConfigBtn(_isAdmin) {
if (!_isAdmin) {
return (
<TouchableOpacity style={{ marginRight: 10 }} onPress={() => console.log('Configuraciones')} >
<Icon style={{ color: 'white' }} name='cog' size={30} />
</TouchableOpacity>
)
}
}
return (
<Stack.Navigator>
<Stack.Screen
name="MainMenu"
component={MainMenu}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<View style={{ flexDirection: 'row' }}>
{renderConfigBtn(isAdmin)}
{renderUserMenu()}
</View>
),
headerStyle: { backgroundColor: '#0064c8' },
}}
/>
<Stack.Screen
name="Profile"
component={Profile}
options={{
headerStyle: { backgroundColor: '#0064c8' },
}}
/>
</Stack.Navigator>
)
}
export default MainStack;
Also, this stack is inside a navigation container as follows:
import React from 'react';
import { useSelector } from "react-redux";
import { NavigationContainer } from "#react-navigation/native";
import AuthStack from "./authStack";
import MainStack from "./mainStack";
const AppNavigator = props => {
const isAuth = useSelector(state => !!state.auth.access_token);
return (
<NavigationContainer>
{ !isAuth && <AuthStack/>}
{ isAuth && <MainStack/>}
</NavigationContainer>
);
};
export default AppNavigator;
I would appreciate any help.
You can access 'navigation' in options like below
options={({navigation})=>({
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<View style={{ flexDirection: 'row' }}>
{renderConfigBtn(isAdmin,navigation)}
{renderUserMenu(navigation)}
</View>
),
headerStyle: { backgroundColor: '#0064c8' },
})}
Basically you can pass a function as a prop to options and navigation will be passed to it as a parameter.
function renderUserMenu(navigation) {
return (
<TouchableOpacity style={{ marginRight: 20 }} onPress={() => navigation.navigate('YOUR SCREEN') } >
<Icon style={{ color: 'white' }} name='user-circle-o' size={30} />
</TouchableOpacity>
)
}
And you can change the renderUserMenu function like above so that it will do the navigation as required.
Use navigation options and then pass it to the function to navigate to profile:
<Stack.Screen
name="MainMenu"
component={MainMenu}
options={({ navigation }) => ({ ......
We simply can import the useNavigation hook from the react-navigation/native package and can implement navigation with the use of this hook without accessing the navigation props from the component.
For Ex.
First import the hook,
import { useNavigation } from '#react-navigation/native';
Use the hook to implement navigation as below in MainStack.js:
const navigation = useNavigation();
function renderUserMenu() {
return (
<TouchableOpacity style={{ marginRight: 20 }} onPress={() => navigation.navigate("Profile") } >
<Icon style={{ color: 'white' }} name='user-circle-o' size={30} />
</TouchableOpacity>
)
}

adjusting Switch and Icon in stack header on react-navigation

I was trying to put an Switch and an Icon on header created with stack navigation from react navigation. the problem I am facing is that adjusting the switch and icon accordingly next to each other. I tried different alternatives but still I am unable to adjust them. I wanted to show on the header a text in the center; Home for instance, and show also a switch and icon on the right of the header next to each other (switch and icon). I will highly appreciate any help of how Can I do this? Here I am sharing my code trying to do it.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow strict-local
*/
import React, {useState} from 'react';
import {StatusBar, View, TouchableOpacity} from 'react-native';
import {
NavigationContainer,
DarkTheme as navigationDarkTheme,
DefaultTheme as navigationDefaultTheme,
useTheme
} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import {
DarkTheme as PaperDarkTheme,
Provider as PaperProvider,
DefaultTheme as PaperDefaultTheme,
Text,
Title,
TouchableRipple,
Switch,
} from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
const customDarktheme = {
...PaperDarkTheme,
...navigationDarkTheme,
colors: {
...PaperDarkTheme.colors,
...navigationDarkTheme.colors,
headerColor: 'rgb(255, 255, 255)',
bgcolor: "#404040",
surface:"#404040",
btnSearchColor:"#404040",
}
}
const customDefaulttheme = {
...PaperDefaultTheme,
...navigationDefaultTheme,
colors: {
...PaperDefaultTheme.colors,
...navigationDefaultTheme.colors,
headerColor: "white",
bgcolor: "#fafcff",
btnSearchColor:"#006aff",
surface:"white",
}
}
const HomeS = () => {
return <View></View>;
};
const Stack = createStackNavigator();
const App = () => {
const {colors} = useTheme()
const [isDarktheme, setDarkTheme] = useState(false);
const togglemethod = () => {
setDarkTheme(!isDarktheme);
};
return (
<>
<PaperProvider theme={isDarktheme?customDarktheme:customDefaulttheme}>
<NavigationContainer theme={isDarktheme?customDarktheme:customDefaulttheme}>
<StatusBar barStyle="dark-content" />
<Stack.Navigator screenOptions={{headerTitleAlign: 'center', headerStyle: { backgroundColor: colors.headerColor }}}>
<Stack.Screen
name="Home"
component={HomeS}
options={{
headerTitle: (props) => (
<View style={{flexDirection: 'row', width:"300%"}}>
<>
<View>
<Title style={{paddingLeft: 180}}>
<Text>Home</Text>
</Title>
</View>
<View >
<TouchableRipple rippleColor="rgba(0, 0, 0, .32)">
<Switch
value={isDarktheme}
color="yellow"
onValueChange={() => togglemethod()}
style={{
paddingLeft:250,
}}
/>
</TouchableRipple>
</View>
<View style={{}}>
<MaterialCommunityIcons
name={
isDarktheme
? 'moon-waning-crescent'
: 'white-balance-sunny'
}
size={25}
color={isDarktheme ? "yellow" : "blue"}
style={{
paddingLeft: 110,
// paddingBottom: 5,
width: '200%',
flexDirection: 'row',
paddingRight:300
}}
/>
</View>
</>
</View>
),
}}
/>
</Stack.Navigator>
</NavigationContainer>
</PaperProvider>
</>
);
};
export default App;
currently the header looks like this:
look at the distance b/w the switch and Icon. trying to eliminate this was not possible for me. for example the text Home disappears while adjusting other elements like switch or Icon. I know this can be achieved. but I run out of options and glad that someone else can do it and learn from.
Since you want to add the switch and icon on the right, you should use headerRight instead of headerTitle
options={{
headerRight: () => (
<View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
// Your switch and icon here
</View>
),
}}

× TypeError: Cannot read property 'navigate' of undefined on React-Native expo react-navigation 5.xx

If somebody can help me.
I'm sure that I miss something, but I can't see it.I'm trying to do navigation between 3 components and I create it with the documentation from the react-navigation page, I try a lot of different ways to do it, but always receive the same answer, so if somebody can see the mistake I do please tell me.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Header from './app/components/header/header'
import Main from './app/components/main/main';
import Data from './app/components/data/data';
import Grafic from './app/components/grafic/grafic';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import ButtonImage from './app/components/buttonImag/buttonImg'
import 'react-native-gesture-handler';
const Stack = createStackNavigator();
function MainScreen({ navigation }) {
return (
<ButtonImage onPress={() => navigation.navigate('Main')} heightV={40} widthV={40} itemImage={require('./app/resourse/home.png')} heightI={33} widthI={33} ></ButtonImage>
);
};
function DataScreen({ navigation }) {
return (
<ButtonImage onPress={() => navigation.navigate('Data')} heightV={40} widthV={40} itemImage={require('./app/resourse/datalist.png')} heightI={33} widthI={33} ></ButtonImage>
);
};
function GraficScreen({ navigation }) {
return (
<ButtonImage onPress={() => navigation.navigate('Grafic')} heightV={40} widthV={40} itemImage={require('./app/resourse/grafic.png')} heightI={33} widthI={33} ></ButtonImage>
);
};
const Footer = ({ navigation }) => {
return (
<View style={styles.header}>
<View style={styles.buttonPos}>
<MainScreen />
<DataScreen />
<GraficScreen />
</View>
</View>
)
};
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Main" component={() => <Main />} />
<Stack.Screen name="Data" component={() => <Data />} />
<Stack.Screen name="Grafic" component={() => <Grafic />} />
</Stack.Navigator>
);
}
const App = () => {
return (
<View style={{ flex: 1 }}>
<Header />
<NavigationContainer>
<MyStack />
</NavigationContainer>
<Footer />
</View>
);
}
const styles = StyleSheet.create({
header: {
backgroundColor: "#00BFFF",
height: 55
},
buttonPos: {
flex: 1,
flexDirection: 'row',
alignItems: "center",
justifyContent: "space-between",
padding: 7
},
conection: {
flex: 1,
flexDirection: 'row',
alignItems: "center",
justifyContent: "space-between",
padding: 7
}
});
export default App
What you need to do that is to use a Tab Navigator.
react-navigation v5 has 3 ways to do so:
createBottomTabNavigator
createMaterialBottomTabNavigator (very easy to customize)
createMaterialTopTabNavigator (with tabBarPosition: 'bottom')
Also you can customize the tabs by reading the documentation.
I made you a basic example using this last option:
import { SafeAreaView, View, Text, StyleSheet } from 'react-native'
import { NavigationContainer } from '#react-navigation/native'
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs'
import { createStackNavigator } from '#react-navigation/stack'
const Header = () => <View style={styles.header}><Text>Header title</Text></View>
const Main = () => <View style={styles.component}><Text>Main component</Text></View>
const Data = () => <View style={styles.component}><Text>Data component</Text></View>
const Grafic = () => <View style={styles.component}><Text>Grafic component</Text></View>
const footerConfig = {
tabBarPosition: 'bottom',
}
const Tabs = createMaterialTopTabNavigator()
const MyFooter = () => (
<Tabs.Navigator {...footerConfig}>
<Tabs.Screen name="Main" component={Main} />
<Tabs.Screen name="Data" component={Data} />
<Tabs.Screen name="Grafic" component={Grafic} />
</Tabs.Navigator>
)
const stackConfig = {
headerMode: 'none',
}
const Stack = createStackNavigator()
const MyStack = () => (
<Stack.Navigator {...stackConfig}>
<Stack.Screen name="Tabs" component={MyFooter} />
</Stack.Navigator>
)
export default () => (
<SafeAreaView style={styles.main}>
<Header />
<NavigationContainer>
<MyStack />
</NavigationContainer>
</SafeAreaView>
)
const styles = StyleSheet.create({
main: {
flex: 1,
},
header: {
height: 64,
width: '100%',
justifyContent: 'center',
alignItems: 'center',
},
component: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
})
EDIT:
You can use useNavigation hook inside your components. If you want to use your current configuration.
https://reactnavigation.org/docs/use-navigation/useNavigation
Also in order to send navigation props to components this:
<Stack.Screen name="Main" component={() => <Main />} />
should become:
<Stack.Screen name="Main" component={Main} />
// or
<Stack.Screen name="Main" component={props => <Main {...props} />} />
Go to you Main, Data, Grafic components and add {navigation} as your function parameter:
function Main({navigation}){
// rest of your codes
}
export default Main