I need to render a modal when the user press in the middle button. I'm using react-native-raw-bottom-sheet library to provide a Modal to my application.
I tried to pass a prop isFocused={props.navigation.isFocused} inside the <Tab.Screen> but the problem is when I pass the props to based if is or not focused in the the is rendered two times instead one.
I also tried to trigger the modal direct by the but without success.
My problematic is, when the user press the i need render the new that will contain the hole logic of the content in the modal and also will render the modal.
My tab.routes.js
import React from 'react';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import TabButton from '../components/Tab/Button';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Home from '../containers/Home';
import Adjustment from '../containers/Adjustment';
import Graphic from '../containers/Graphic';
import Help from '../containers/Help';
import NewTransaction from '../containers/NewTransaction';
const icons = {
Home: {
name: 'home',
},
Graphic: {
name: 'pie-chart',
},
NewTransaction: {
name: 'notifications-none',
},
Help: {
name: 'help-outline',
},
Adjustment: {
name: 'settings',
},
};
const Tab = createBottomTabNavigator();
const TabRoutes = () => (
<Tab.Navigator
initialRouteName="HomeScreen"
screenOptions={({route, navigation}) => ({
tabBarIcon: ({color, size, focused}) => {
if (route.name === 'NewTransaction') {
return <TabButton focused={focused} onPress={() => navigation.navigate('NewTransaction')} />;
}
const {name} = icons[route.name];
return <Icon name={name} size={size} color={color} />;
},
})}
tabBarOptions={{
keyboardHidesTabBar: true,
activeTintColor: '#f8b006',
inactiveTintColor: '#1C3041',
style: {
height: 60,
},
iconStyle: {
marginTop: 5,
},
labelStyle: {
fontSize: 12,
marginBottom: 10,
},
}}>
<Tab.Screen
options={{
title: 'Home',
}}
name="Home"
component={Home}
/>
<Tab.Screen
options={{
title: 'Gráfico',
}}
name="Graphic"
component={Graphic}
/>
<Tab.Screen
options={{
title: '',
}}
name="NewTransaction"
component={NewTransaction}
/>
<Tab.Screen
options={{
title: 'Ajuda',
}}
name="Help"
component={Help}
/>
<Tab.Screen
options={{
title: 'Ajustes',
}}
name="Adjustment">
{(props) => (
<Adjustment isVisible={props.navigation.isFocused()} onPress={() => props.navigation.navigate('Home')} />
)}
</Tab.Screen>
</Tab.Navigator>
);
export default TabRoutes;
Tab/Button/index.js
import React from 'react';
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Button from './styles';
const TabButton = ({onPress, focused}) => {
return (
<TouchableWithoutFeedback onPress={onPress}>
<Button focused={focused}>
<Icon name="add" size={35} color={'white'} />
</Button>
</TouchableWithoutFeedback>
);
};
export default TabButton;
And the component that will display the modal content
import React from 'react';
import {Text, View} from 'react-native';
const NewTransaction = ({isVisible}) => {
return (
<View>
<Text>Welcome to NewTransactions </Text>
</View>
);
};
export default NewTransaction;
This is my home tab it looks like to you
I custom tab bar with code but the add button is not a screen it is just a button and popup options to select
import {hideModalCreate, showModalCreate} from '#features/loading/actions';
import CreateYCTV from '#features/main/CreateYCTV';
import HomeScreen from '#features/main/Home';
import Manager from '#features/main/Manager';
import Notification from '#features/main/Notification';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import images from '#res/icons';
import * as React from 'react';
import {Image, Pressable, View} from 'react-native';
import {Text, useTheme} from 'react-native-paper';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {connect} from 'react-redux';
import ProductStack from './ProductStack';
const Tab = createBottomTabNavigator();
function MyTabBar({
state,
descriptors,
navigation,
showModalCreate,
hideModalCreate,
isShowModalCreate,
}) {
const {colors} = useTheme();
const insets = useSafeAreaInsets();
const focusedOptions = descriptors[state.routes[state.index].key].options;
if (focusedOptions.tabBarVisible === false) {
return null;
}
return (
<View
style={{
flexDirection: 'row',
backgroundColor: '#FFFFFF',
paddingBottom: Math.max(insets.bottom, 0),
}}>
{state.routes.map((route, index) => {
const {options} = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const getSourceImage = (isFocused) => {
switch (route.name) {
case 'home':
return isFocused ? images.tab_home1 : images.tab_home;
case 'loans':
return isFocused ? images.tab_searching1 : images.tab_searching;
case 'notification':
return isFocused ? images.notifications1 : images.notifications;
case 'manager':
return isFocused
? images.tab_paper_folder1
: images.tab_paper_folder;
default:
return images.tab_add;
}
};
const onPress = () => {
if (route.name === 'create') {
if (isShowModalCreate) {
hideModalCreate();
return;
}
showModalCreate();
return;
}
hideModalCreate();
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
return (
<Pressable
accessibilityRole="button"
accessibilityStates={isFocused ? ['selected'] : []}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
onLongPress={onLongPress}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 8,
backgroundColor: 'white',
}}>
<Image source={getSourceImage(isFocused)} />
{route.name != 'create' ? (
<Text
style={{
color: isFocused ? colors.primary : colors.placeholder,
fontSize: 10,
marginTop: 4,
}}>
{label}
</Text>
) : null}
</Pressable>
);
})}
</View>
);
}
const Tabbar = ({showModalCreate, hideModalCreate, isShowModalCreate}) => {
return (
<Tab.Navigator
tabBar={(props) => (
<MyTabBar
isShowModalCreate={isShowModalCreate}
showModalCreate={showModalCreate}
{...props}
hideModalCreate={hideModalCreate}
/>
)}>
<Tab.Screen
name="home"
component={HomeScreen}
options={{
title: 'Trang chủ',
}}
/>
<Tab.Screen
name="loans"
component={ProductStack}
options={{
title: 'Sản phẩm',
}}
/>
<Tab.Screen name="create" component={CreateYCTV} />
<Tab.Screen
name="notification"
component={Notification}
options={{
title: 'Thông báo',
tabBarBadge: '13',
}}
/>
<Tab.Screen
name="manager"
component={Manager}
options={{
title: 'Quản lý',
}}
/>
</Tab.Navigator>
);
};
const mapStateToProps = (state, ownProps) => {
return {
isShowModalCreate: state.loading.isShowModalCreate,
};
};
const mapDispatch = {
showModalCreate: showModalCreate,
hideModalCreate: hideModalCreate,
};
export default connect(mapStateToProps, mapDispatch)(Tabbar);
Related
I am unable to pass the photo data from the takePicture function in CameraScreen.tsx to the AddNewPostButton.tsx component to insert the photo into the firebase database. My idea of using useContext is not working and I don't know how to solve it.
I tried to send photos from the CameraScreen screen to the AddNewPostButton component using the useContext mechanism, but I couldn't do it and I don't know how to do it.
CameraScreen.tsx
"Cannot assign argument of type 'string | undefined' to parameter of type 'string | null'.
Type 'undefined' cannot be assigned to type 'string | null'."
import React, { useContext, useState, createContext } from 'react';
import { Pressable, Center, Modal, Button, Stack, Input, TextArea, Icon, Text, View } from 'native-base';
import { MaterialCommunityIcons } from '#expo/vector-icons';
import { addDoc, Timestamp, collection } from 'firebase/firestore';
import { db } from '../firebaseConfig';
import { AuthContext } from '../utils/AuthStateListener';
import type { StackNavigationProp } from '#react-navigation/stack';
import { useNavigation } from '#react-navigation/native';
import { RootStackParamList } from '../stacks/RootStack';
type photoButton = StackNavigationProp<RootStackParamList>;
export const ImageContext = createContext<{ image: string | null, setImage: (newImage: string | null) => void }>({
image: null,
setImage: () => {},
});
function AddNewPostButton() {
const { currentUser, userProfile } = useContext(AuthContext);
//const [image, setImage] = useState<string | null>(null);
const navigation: photoButton = useNavigation();
const { image, setImage } = useContext(ImageContext);
/* const setImage = (newImage: string | null) => {
ImageContext.setImage(newImage);
} */
const [modalVisible, setModalVisible] = React.useState(false);
const [newPost, setNewPost] = React.useState({
title: '',
description: '',
location: '',
photo: ''
});
if (image) console.log(image);
const handleChange = (name: string, value: string) => {
setNewPost((prev) => ({ ...prev, [name]: value }));
};
const addNewPost = async () => {
await addDoc(collection(db, 'publicPosts'), {
title: newPost.title,
description: newPost.description,
location: newPost.location,
date: Timestamp.fromDate(new Date()),
photo: image ? image : '',
likes: 0,
authorId: currentUser?.uid,
authorName: userProfile?.name,
});
setNewPost({
title: '',
description: '',
location: '',
photo: ''
});
setModalVisible(false);
setImage('');
};
return (
<>
<Modal isOpen={modalVisible} onClose={setModalVisible}>
<Modal.Content>
<Modal.CloseButton />
<Modal.Header>Dodaj ogłoszenie</Modal.Header>
<Modal.Body>
<Stack space={1} w="75%" maxW="300px" mx="auto">
<Input
variant="outline"
placeholder="Tytuł nowego ogłoszenia"
onChangeText={(value) => handleChange('title', value)}
/>
<Input
variant="outline"
placeholder="Lokalizacja"
onChangeText={(value) => handleChange('location', value)}
/>
<TextArea
variant="outline"
placeholder="Opis ogłoszenia"
onChangeText={(value) => handleChange('description', value)}
/>
</Stack>
<View>
<Button
onPress={() => {
navigation.navigate({
name: 'Camera',
key: 'CameraScreen',
});
//navigation.navigate({name:'Camera'})
setModalVisible(false);
}}>
<Text>Dodaj zdjęcie</Text>
</Button>
</View>
</Modal.Body>
<Modal.Footer>
<Button.Group space={2}>
<Button
variant="ghost"
colorScheme="blueGray"
onPress={() => {
setModalVisible(false);
}}>
Anuluj
</Button>
<Button
onPress={() => {
addNewPost();
}}>
Zapisz
</Button>
</Button.Group>
</Modal.Footer>
</Modal.Content>
</Modal>
<Pressable
opacity={modalVisible === true ? 1 : 0.5}
py="3"
flex={1}
onPress={() => {
setModalVisible(true);
}}>
<Center>
<Icon mb="1" as={<MaterialCommunityIcons name="plus-box" />} color="white" size="sm" />
<Text color="white" fontSize="12">
Dodaj
</Text>
</Center>
</Pressable>
</>
);
}
export default AddNewPostButton;
import { Camera, CameraType } from 'expo-camera';
import { useState, useRef, useContext } from 'react';
import { StyleSheet, Text, TouchableOpacity, View, Dimensions, Alert } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import { ImageContext } from '../components/AddNewPostButton';
const screenWidth = Dimensions.get('window').width;
function CameraScreen() {
const [type, setType] = useState(CameraType.back);
const [permission, requestPermission] = Camera.useCameraPermissions();
const { image, setImage } = useContext(ImageContext);
let camera = useRef<Camera>(null);
if (!permission) {
return <View />;
}
if (!permission.granted) {
return (
<Text>Brak dostępu do kamery</Text>
);
}
const takePicture = async () => {
if (camera.current) {
try {
const options = { quality: 0.5, base64: true, orientation: 'portrait' };
const data = await camera.current.takePictureAsync(options);
setImage(data.base64);
} catch (error) {
console.error(error);
Alert.alert('Wystąpił błąd podczas dodawania zdjęcia');
}
}
}
return (
<View style={styles.container}>
<Camera ref={camera} style={styles.camera} type={type}>
<View style={styles.cameraControls}>
<TouchableOpacity
style={styles.flipButton}
onPress={() => {
setType(
type === CameraType.back
? CameraType.front
: CameraType.back
);
}}>
<Ionicons name="ios-camera-reverse-outline" size={64} color="#fff" />
</TouchableOpacity>
<TouchableOpacity
style={styles.captureButton}
onPress={() => takePicture()}>
<Ionicons name="ios-camera" size={64} color="#fff" />
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'center',
},
camera: {
width: screenWidth,
height: screenWidth + 200,
},
cameraControls: {
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'space-evenly',
flexDirection: 'row',
paddingHorizontal: 16,
paddingVertical: 16,
},
flipButton: {
alignSelf: 'flex-end',
},
captureButton: {
alignSelf: 'flex-end',
},
text: {
fontSize: 14,
},
});
export default CameraScreen;
import React, { useContext } from 'react';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { Timestamp } from 'firebase/firestore';
import ToggleDarkMode from '../components/DarkModeToggle';
import Header from '../components/Header';
import HomeScreen from '../screens/HomeScreen';
import PostDetailsScreen from '../screens/PostDetailsScreen';
import Signup from '../screens/SignupScreen';
import SigninScreen from '../screens/SigninScreen';
import { AuthContext } from '../utils/AuthStateListener';
import ResetPassword from '../screens/ResetPassword';
import HeaderNavigateBackButton from '../components/HeaderNavigateBackButton';
import UserDetailsScreen from '../screens/UserDetailsScreen';
import CameraScreen from '../screens/CameraScreen';
export type RootStackParamList = {
Home: undefined;
Signup: undefined;
Signin: undefined;
ResetPassword: undefined;
UserDetails: undefined;
Camera: {
param: string;
};
Details: {
post: {
title: string;
description: string;
date: Timestamp;
location: string;
likes: number;
};
};
};
const Stack = createNativeStackNavigator<RootStackParamList>();
// Functions to prevent eslint error of declaring component inside parent component
function ToggleDarkModeComponent() {
return <ToggleDarkMode />;
}
function HeaderComponent() {
return <Header />;
}
function NavigateBackButtonComponent() {
return <HeaderNavigateBackButton />;
}
function RootStack() {
const { currentUser } = useContext(AuthContext);
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#86efac' },
headerRight: ToggleDarkModeComponent,
}}>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: HeaderComponent,
}}
/>
{!currentUser ? (
<>
<Stack.Screen
name="Signin"
component={SigninScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="Signup"
component={Signup}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="ResetPassword"
component={ResetPassword}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
</>
) : (
<>
<Stack.Screen
name="Details"
component={PostDetailsScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="UserDetails"
component={UserDetailsScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="Camera"
component={CameraScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
</>
)}
</Stack.Navigator>
);
}
export default RootStack;
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>
);
}
I am make using react-native. I recently had to update my react-navigation package to version 6. The issue is my drawer will no longer open and I cannot figure out how to fix it.
This is may code for my navigation:
import React from 'react';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createStackNavigator } from '#react-navigation/stack';
import { StyleSheet, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/dist/FontAwesome5';
import IonIcon from 'react-native-vector-icons/dist/Ionicons';
import { useNavigation } from '#react-navigation/native';
import { HomeScreen } from '../../screens/app/home/home.screen';
import HistoryScreen from '../../screens/app/history/history.screen';
import { SignoffScreen } from '../../screens/app/signoff/signoff.screen';
import NotificationsScreen from '../../screens/app/notifications/notifications.screen';
import useTheme from '../../theme/hooks/useTheme';
import { AppStackList, AppStackProps, DrawerList } from './types';
import { Colors } from '../../theme/Variables';
import CustomDrawerContent from '../../components/molecules/custom-drawer';
import { common } from '../../theme/Common';
import { FormScreen } from '../../screens/app/form/form.screen';
import { Menu } from '../../assets';
const AppStack = createStackNavigator<AppStackList>();
const Drawer = createDrawerNavigator<DrawerList>();
const renderIcon = (name: string, ion: boolean) => {
if (ion) {
return <IonIcon name={name} style={styles.iconStyle} />;
}
return <Icon name={name} style={styles.iconStyle} />;
};
const NotificationsNavigator = () => {
const { Gutters } = useTheme();
const navigation = useNavigation<AppStackProps>();
return (
<TouchableOpacity
style={(common.navIconStyle, Gutters.regularRMargin)}
delayPressIn={0}
onPress={navigation.navigate('Notifications', { screen: 'NotificationsScreen' })}
>
<IonIcon name="notifications-outline" style={common.navIconStyle} />
</TouchableOpacity>
);
};
const MenuNavigator = () => {
const navigation = useNavigation<AppStackProps>();
return (
<TouchableOpacity>
<Menu name="notifications-outline" style={common.navIconStyle} />
</TouchableOpacity>
);
};
const historyDrawerOptions = {
headerShown: false,
title: '',
drawerIcon: () => renderIcon('tasks', false),
headerTintColor: Colors.black,
headerRight: NotificationsNavigator,
};
const AppNavigator = () => {
const { Custom } = useTheme();
return (
<AppStack.Navigator screenOptions={Custom.globalNavigatorScreenOptions}>
<AppStack.Screen
name="App Home"
component={DrawerNavigator}
options={{ headerShown: false }}
/>
<AppStack.Screen
name="NotificationsScreen"
component={NotificationsScreen}
options={{ headerShown: false }}
/>
<AppStack.Screen name="FormScreen" component={FormScreen} options={{ headerShown: false }} />
<AppStack.Screen
name="SignoffScreen"
component={SignoffScreen}
options={{ headerShown: false }}
/>
</AppStack.Navigator>
);
};
const DrawerNavigator = () => (
<Drawer.Navigator
drawerStyle={styles.drawerStyle}
drawerContentOptions={{
activeTintColor: Colors.primary,
inactiveTintColor: Colors.white,
labelStyle: {
color: Colors.white,
},
}}
drawerContent={() => <CustomDrawerContent />}
>
<Drawer.Screen
name="Home"
component={HomeScreen}
options={{
headerShown: true,
headerTitle: '',
headerTransparent: true,
headerStyle: {
height: 120,
backgroundColor: '#fff',
},
headerTitleAlign: 'center',
headerTintColor: '#002C5F',
headerRight: NotificationsNavigator,
headerRightContainerStyle: {
width: 100,
marginRight: 8,
},
headerLeft: MenuNavigator,
drawerActiveTintColor: Colors.white,
drawerInactiveTintColor: Colors.white,
drawerLabelStyle: { fontSize: 15 },
}}
/>
<Drawer.Screen name="History" component={HistoryScreen} options={historyDrawerOptions} />
</Drawer.Navigator>
);
export default AppNavigator;
The draw was working before the update but now after it wont open? My NotificationsNavigator will also not open to its screen. Can anyone help???
I am learning react-native by reading some blogs and trying to replicate the concepts on a test application that uses Drawer (Home and About screens), Stack (Home -> Settings) and Tab (Home and About Screens) navigations. So far, so good.
I am using 'react-native-localize', 'i18n-js' and 'lodash.memoize' in order to make the app multi language. During its startup, the application successfully determines the current locale from the device settings and display the correct contents based on the locale.
However, I want to be able to change it dynamically by pressing the toggle language button on Settings screen. I don't know how to change the locale and make the whole application (drawer menu option names, bottom tab names and all screens text contents) re-render and display text from the new locale. Could someone help me, please?
App.js
import * as i18n from './src/utils/i18n';
const App = () => {
const [locale, setLocale] = useState(i18n.DEFAULT_LANGUAGE);
const localizationContext = useMemo(
() => ({
t: (scope, options) => i18n.t(scope, {locale, ...options}),
locale,
setLocale
}),
[locale],
);
const handleLocalizationChange = useCallback(
(newLocale) => {
const newSetLocale = i18n.setI18nConfig(newLocale);
setLocale(newSetLocale);
},
[locale],
);
useEffect(() => {
handleLocalizationChange();
RNLocalize.addEventListener('change', handleLocalizationChange);
return () => {
RNLocalize.removeEventListener('change', handleLocalizationChange);
};
}, []);
return (
<LocalizationContext.Provider value={localizationContext}>
<NavigationContainer>
<DrawerNavigator localizationChange={handleLocalizationChange}/>
</NavigationContainer>
</LocalizationContext.Provider>
);
};
export default App;
i18n.js
import {I18nManager} from 'react-native';
import * as RNLocalize from 'react-native-localize';
import i18n from 'i18n-js';
import memoize from 'lodash.memoize';
export const DEFAULT_LANGUAGE = 'en';
export const translationGetters = {
'en': () => require('../locales/en.json'),
'es': () => require('../locales/es.json'),
};
export const translate = memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key),
);
export const t = translate;
export const setI18nConfig = (codeLang = null) => {
// Fallback if no available language fits
const fallback = {languageTag: DEFAULT_LANGUAGE, isRTL: false};
const lang = codeLang ? {languageTag: codeLang, isRTL: false} : null;
const {languageTag, isRTL} = lang
? lang
: RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
fallback;
// Enables fallbacks
i18n.fallbacks = true;
// Clear translation cache
translate.cache.clear();
// Update layout direction
I18nManager.forceRTL(isRTL);
// Set i18n-js config
i18n.translations = {[languageTag]: translationGetters[languageTag]()};
i18n.locale = languageTag;
return languageTag;
};
DrawerNavigation.js
import React, { useContext } from "react";
import { AboutStackNavigator } from "./StackNavigator";
import TabNavigator from "./TabNavigator";
import { createDrawerNavigator } from "#react-navigation/drawer";
import LocalizationContext from '../providers/LocalizationContext';
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Drawer.Navigator
drawerContentOptions={{
activeTintColor: 'gray',
inactiveTintColor: 'gray',
}}
>
<Drawer.Screen
name="Home"
component={ TabNavigator }
options={{
title: t('title_home')
}}
/>
<Drawer.Screen
name="About"
component={ AboutStackNavigator }
options={{
title: t('title_about')
}}
/>
</Drawer.Navigator>
);
};
export default DrawerNavigator;
StackNavigator.js
import React, { useContext } from "react";
import { createStackNavigator } from "#react-navigation/stack";
import Home from "../screens/Home";
import About from "../screens/About";
import Settings from "../screens/Settings";
import LocalizationContext from '../providers/LocalizationContext';
const Stack = createStackNavigator();
const MainStackNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen name="Home" component={ Home } options={{ headerShown:false }} />
<Stack.Screen name="About" component={ About } options={{ title: t('title_about') }}/>
<Stack.Screen name="Settings" component={ Settings } options={{ title: t('title_settings') }}/>
</Stack.Navigator>
);
};
const AboutStackNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen name="About" component={ About } options={{ title: t('title_about') }} />
</Stack.Navigator>
);
};
const SettingsStackNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen name="Settings" component={ Settings } options={{ title: t('title_settings') }} />
</Stack.Navigator>
);
};
const screenOptionStyle = {
headerStyle: {
backgroundColor: 'white',
},
headerTintColor: "gray",
headerBackTitle: "Back",
};
export {
MainStackNavigator,
AboutStackNavigator,
SettingsStackNavigator
};
TabNavigator.js
import React, { useContext } from 'react';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { MainStackNavigator, AboutStackNavigator } from './StackNavigator';
import LocalizationContext from '../providers/LocalizationContext';
const Tab = createBottomTabNavigator();
const BottomTabNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Tab.Navigator
tabBarOptions={{
activeTintColor: 'black',
inactiveTintColor: 'gray',
activeBackgroundColor: 'white',
inactiveBackgroundColor: 'white',
labelStyle: {
fontSize: 12,
},
}} >
<Tab.Screen name="Home" component={ MainStackNavigator } options={{ title: t('title_home') }} />
<Tab.Screen name="About" component={ AboutStackNavigator } options={{ title: t('title_about'), headerShown:false }} />
</Tab.Navigator>
);
}
export default BottomTabNavigator;
Home.js
import React, { useContext } from "react";
import { View, SafeAreaView, StyleSheet, Text, StatusBar } from "react-native";
import { Header } from 'react-native-elements';
import LocalizationContext from '../providers/LocalizationContext';
const Home = ({ navigation }) => {
const {t} = useContext(LocalizationContext);
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor='white' />
<Header
statusBarProps={{ barStyle: 'dark-content', backgroundColor: 'gray' }}
placement="center"
leftComponent={{ icon: 'menu', color: 'white', onPress: () => navigation.openDrawer() }}
centerComponent={{ text: 'My App', style: styles.centerComponent }}
rightComponent={{ icon: 'settings', color: 'white', onPress: () => navigation.navigate("Settings") }}
containerStyle={{ backgroundColor: 'gray', justifyContent: 'space-around' }}
/>
<View style={styles.center}>
<Text>{t('text_home')}</Text>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
center: {
flex: 1,
backgroundColor: 'white',
justifyContent: "center",
alignItems: "center",
textAlign: "center",
},
container: {
flex: 1,
backgroundColor: 'white',
},
centerComponent: {
color: 'white',
fontWeight: 'bold',
fontSize: 20
},
});
export default Home;
Settings.js
import React, { useContext, useState } from "react";
import { View, StyleSheet, Text, StatusBar, Button } from "react-native";
import LocalizationContext from '../providers/LocalizationContext';
const Settings = () => {
const {t} = useContext(LocalizationContext);
const [locale, setLocale] = useState('en');
const toggleLanguage = (locale) => {
setLocale(locale);
console.log(locale);
};
return (
<View style={styles.center}>
<StatusBar barStyle="dark-content" backgroundColor='white' />
<View style={styles.center}>
<Text>{t('text_setings')}</Text>
<Button onPress={() => toggleLanguage(locale == "es" ? "en" : "es")} title={locale == "es" ? "EN" : "ES"} />
</View>
</View>
);
};
const styles = StyleSheet.create({
center: {
flex: 1,
backgroundColor: 'white',
justifyContent: "center",
alignItems: "center",
textAlign: "center",
},
});
export default Settings;
To allow changes in language display, you may use the following method (assuming that you want to display English , French and German) :
make a globals.js with the following (add more items if you need):
module.exports = {
eunit:["Eldery Unit", "Unité des personnes âgées", "Senioreneinheit"],
skeysearch:["Search by Name", "Recherche par nom", "Suche mit Name"],
sstatus: ["Status", "Statut", "Status"]
};
include the following line at the top of your js pages:
GLOBAL = require('./globals');
In the places where you want to display different language, use the following:
GLOBAL.skeysearch[this.state.lang]
change the lang state according to your needs. (say by clicking a button),
So ,
this.state.lang=0 for English
this.state.lang=1 for French
this.state.lang=2 for German
I am trying to add custom icon to my CustomDrawerComponent, but nothing happen...
App.js :
const navigationOptions = {
headerTintColor: colors.white,
};
const drawerNavigationOption = ({ navigation }) => ({
...navigationOptions,
headerLeft: (
<TouchableOpacity onPress={() => navigation.toggleDrawer()}>
<View>
<Icon name="menu" size={24} color={colors.white} />
</View>
</TouchableOpacity>
),
});
const MapsStackNavigator = createStackNavigator({
MapsNavigator: {
screen: MapsScreen,
navigationOptions: drawerNavigationOption,
},
});
const AppDrawerNavigator = createDrawerNavigator(
{
Plans: MapsStackNavigator,
},
{
contentComponent: CustomDrawerMenu,
contentOptions: {
inactiveTintColor: colors.doveGrey,
activeTintColor: colors.doveGrey,
},
}
);
My CustomDrawerMenu.js :
export default class CustomDrawerMenu extends Component {
render() {
return (
<ScrollView
contentContainerStyle={{
flex: 1,
flexDirection: "column",
justifyContent: "space-between",
}}
>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
{...}
<DrawerItems {...this.props} />
</SafeAreaView>
{...}
</ScrollView>
);
}
}
My MapsScreen :
export default class MapsScreen extends React.Component {
static navigationOptions = {
drawerIcon: (
<Image
style={{ width: 24, height: 24 }}
source={require("../../assets/icons/plan.png")}
/>
),
title: "Plans",
};
render() {
return (
<Text>My map screen</Text>
);
}
}
But absolutely nothing happened... I tried to add drawerIcon to my App.js > const navigationOptions but nothing happened aswell.
I do not really know where to place drawerIconm because I search on the doc, on some YouTubes video and when I reproduced the same, it does not work.
Thank you.
In the new version of react-navigation(5.x)
You have to do :
1-
import { createDrawerNavigator } from '#react-navigation/drawer';
import { NavigationContainer } from '#react-navigation/native';
import Icon from 'react-native-vector-icons/Ionicons';
2- Instead of using createDrawerNavigator you have to use Drawer.Navigator as below :
<NavigationContainer>
<Drawer.Navigator
initialRouteName="Products">
<Drawer.Screen name="Products" component={YOUR COMPONENT OR YOUR STACKNAVIGATOR} options={{
drawerIcon: config => <Icon
size={23}
name={Platform.OS === 'android' ? 'md-list' : 'ios-list'}></Icon>
}} />
<Drawer.Screen name="Orders" component={YOUR COMPONENT OR YOUR STACKNAVIGATOR} options={{
drawerIcon: config => <Icon
size={23}
name={Platform.OS === 'android' ? 'md-create' : 'ios-create'}></Icon>
}} />
</Drawer.Navigator>
</NavigationContainer>
I finally found the answer myself, you can not add drawerIcon to navigationOptions of the child-screen. You have to do like so :
const AppDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: HomeStackNavigator,
navigationOptions: {
drawerIcon: (
<Image
style={{ width: 24, height: 24 }}
source={require("./assets/icons/plan.png")}
/>
),
},
},
And then in your HomeStack :
const HomeStackNavigator = createStackNavigator({
HomeNavigator: {
screen: HomeScreen,
navigationOptions: drawerNavigationOption,
},
});
Hope it'll serve to someone !
<Stack.Screen name="Feed" component={Feed} options={{ title: 'Feed',
drawerIcon: ({ focused, size }) => (
<Image
source={require('../../../assets/icons/icon-email.png')}
style={[{ height: 20, width: 20 }]}
/> )
}} />
const AppDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: HomeStackNavigator,
navigationOptions: {
drawerIcon: (
<View>
<Image
style={{ width: 24, height: 24 }}
source={require("./assets/icons/plan.png")}
/>
</View>
),
},
},
Add before you will get original image style
hope this will save someone days..
import { NavigationContainer } from "#react-navigation/native";
<NavigationContainer>
<Drawer /> //import from your folder/file
</NavigationContainer>
drawer file
import { createDrawerNavigator } from "#react-navigation/drawer";
import DrawerContain from "./DrawerContain";
import StackNavigatore from "./stackNavigatore";
import ProductHome from "../product/ProductHome";
import Contact from "./ContactUs";
import About from "./About";
import HomeOrder from "./orderStack";
function DrawerNavigator() {
return (
<Drawer.Navigator
drawerContent={(props) => <DrawerContain {...props} />}
drawerContentOptions={
{
// activeTintColor: "#e91e63",
// itemStyle: { marginVertical: 5 },
}
}
>
<Drawer.Screen name="Home" component={StackNavigatore} />
<Drawer.Screen
name="Order"
component={HomeOrder}
/>
<Drawer.Screen name="Contact Us" component={Contact} />
<Drawer.Screen name="About Us" component={About} show={false} />
</Drawer.Navigator>
);
}
export default DrawerNavigator;
drawer container file
import {
DrawerContentScrollView,
DrawerItemList,
DrawerItem,
} from "#react-navigation/drawer";
import { View, StyleSheet } from "react-native";
import { useNavigation } from "#react-navigation/native";
import React from "react";
import Ionicons from "react-native-vector-icons/Ionicons";
import { Drawer, Text } from "react-native-paper";
function DrawerContain({ ...props }) {
// const navigation = useNavigation();
const image = require("../../assets/img/rupee.png");
return (
<>
<Drawer.Section>
<DrawerItem
icon={({ color, size }) => (
<Ionicons name="home-outline" color={color} size={size} /> <<--- with
vectore icon
)}
label="Sell prodcuts to customer"
// onPress={() => props.navigation.navigate('route to screen')}
/>
<Drawer.Item
icon={image} <<---- from local storage
label="Orders"
onPress={() => props.navigation.navigate("Order")}
/>
</>
)
}