UI not updating after adding or removing items with apollo cache and reactive variables - react-native

I'm having a hard time updating my UI after adding or removing items on my react native app with apollo cache.
To explain a little bit more. I have an
explorer Screen where are displayed some items with a toggle to subscribe or unsubscribe to an item. To another screen called "Subscribe Screen", I'm supposed to displayed all my favorite items. So I created a reactive variable called allFavoritesVar where I can add or delete items from there.
So in my cache.js I have :
import {InMemoryCache} from '#apollo/client/core';
import {makeVar} from '#apollo/client';
export const allFavoritesVar = makeVar([]);
export const cache = new InMemoryCache({
Query: {
fields: {
userFavorites: {
read() {
return allFavoritesVar();
},
},
}
}
})
So on my Explorer screen i'm checking if each item exist in allFavoritesVar to make the toggle red and inform the user that these items are already in their "Subscribe Screen".
const favExists = (flux) => {
if (allFavoritesVar().filter((item) => item.id === flux.id).length > 0) {
return true;
}
return false;
};
I was using redux before and switch to apollo because I needed to persist the cache when users were opening their app. Everything was much simpler with redux, the toggle were working fine and becoming red or grey when adding or removing items from the store and the "subscribe screen" was updating itself.
Now when I'm toggling, the mutation works, I can see that items are added or removed, but my ui is not updating. And when I'm closing my app, the last state of the cache is not displayed.
Here is my Explorer Screen :
import React, {useEffect, useState} from 'react';
import {
SafeAreaView,
StyleSheet,
Dimensions,
ScrollView,
TouchableOpacity,
Image,
FlatList,
ActivityIndicator,
} from 'react-native';
import {
NetworkStatus,
useLazyQuery,
useMutation,
useQuery,
} from '#apollo/client';
import {useSelector, useDispatch} from 'react-redux';
import {Box, Text} from 'react-native-design-utility';
import {Notifier} from 'react-native-notifier';
import {useTheme} from '#react-navigation/native';
import ErrorIcon from 'react-native-vector-icons/Ionicons';
import RefreshIcon from 'react-native-vector-icons/Ionicons';
import {theme} from '../theme/theme';
import Loading from '../components/Loading';
import CustomNotifier from '../components/CustomNotifier';
import CustomNotifierError from '../components/CustomNotifierError';
import SubscribeItem from '../components/SubscribeItem';
import {
SUBSCRIBE_FLUXGROUP_MUTATION,
SUBSCRIBE_FLUX_MUTATION,
UNSUBSCRIBE_FLUXGROUP_MUTATION,
UNSUBSCRIBE_FLUX_MUTATION,
} from '../graphql/mutations/fluxMutations';
import {
GET_EXPLORER_CATEGORIES_QUERY,
GET_EXPLORER_SLIDES_QUERY,
} from '../graphql/queries/explorerQueries';
import ToggleIcon from '../components/ToggleIcon';
import {HEIGHT} from '../utils/constants';
import {ALL_FAVORITES_QUERY} from '../graphql/queries/userQueries';
import {allFavoritesVar, cache} from '../utils/cache';
import {FLUX_QUERY} from '../graphql/queries/fluxesQueries';
const WIDTH = Dimensions.get('window').width;
const PAGE_SIZE = 10;
const ExplorerScreen = ({navigation}) => {
const {colors, dark} = useTheme();
const [limit, setLimit] = useState(PAGE_SIZE);
const [isError, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const {
data: explorerData,
loading: explorerLoading,
error,
refetch,
} = useQuery(GET_EXPLORER_CATEGORIES_QUERY, {
fetchPolicy: 'cache-first',
errorPolicy: 'all',
});
const {data: favoritesData, loading: favLoading} =
useQuery(ALL_FAVORITES_QUERY);
const {data: slidesData, loading: slidesLoading} = useQuery(
GET_EXPLORER_SLIDES_QUERY,
{
fetchPolicy: 'cache-first',
},
);
const [subscribeToFlux] = useMutation(SUBSCRIBE_FLUX_MUTATION);
const [subscribeToFluxGroup] = useMutation(SUBSCRIBE_FLUXGROUP_MUTATION);
const [unsubscribeFromFlux] = useMutation(UNSUBSCRIBE_FLUX_MUTATION);
const [unsubscribeFromFluxGroup] = useMutation(
UNSUBSCRIBE_FLUXGROUP_MUTATION,
);
const addFav = (flux) => {
const explorerFav = allFavoritesVar([...allFavoritesVar(), flux]);
console.log('explorerFav: ', explorerFav);
return explorerFav;
};
const favExists = (flux) => {
if (allFavoritesVar().filter((item) => item.id === flux.id).length > 0) {
return true;
}
return false;
};
const handleAddFavorite = async (flux) => {
if (flux.__typename === 'FluxGroup') {
addFav(flux);
Notifier.showNotification({
title: 'Vous êtes abonné à ce groupe de flux',
Component: CustomNotifier,
componentProps: {
alertType: 'info',
},
});
await subscribeToFluxGroup({
variables: {
id: parseInt(flux.id),
frequency: 'all',
},
});
} else {
addFav(flux);
Notifier.showNotification({
title: 'Vous êtes abonné à ce flux',
Component: CustomNotifier,
componentProps: {
alertType: 'info',
},
});
await subscribeToFlux({
variables: {
id: parseInt(flux.id),
frequency: 'all',
}
});
}
};
const handleRemoveFavorite = async (flux) => {
if (flux.__typename === 'FluxGroup') {
Notifier.showNotification({
title: 'Vous êtes désabonné de ce groupe de flux',
Component: CustomNotifierError,
componentProps: {
alertType: 'error',
},
});
await unsubscribeFromFluxGroup({
variables: {
id: parseInt(flux.id),
},
update: (cache, {data}) => {
const existingFavs = cache.readQuery({
query: ALL_FAVORITES_QUERY,
});
//console.log('DATA UPDATE:', data);
const newFavs = existingFavs.userFavorites.filter(
(item) => item.id !== flux.id,
);
console.log('DATA UPDATE:', newFavs);
cache.writeQuery({
query: ALL_FAVORITES_QUERY,
data: {userFavorites: [newFavs, ...existingFavs.userFavorites]},
});
},
});
} else {
Notifier.showNotification({
title: 'Vous êtes désabonné de ce flux',
Component: CustomNotifierError,
componentProps: {
alertType: 'error',
},
});
await unsubscribeFromFlux({
variables: {
id: parseInt(flux.id),
},
update: (cache, {data}) => {
const existingFavs = cache.readQuery({
query: ALL_FAVORITES_QUERY,
});
//console.log('DATA UPDATE:', data);
const newFavs = existingFavs.userFavorites.filter(
(item) => item.id !== flux.id,
);
console.log('DATA UPDATE:', newFavs);
cache.writeQuery({
query: ALL_FAVORITES_QUERY,
data: {userFavorites: [newFavs, ...existingFavs.userFavorites]},
});
},
});
}
};
function sliceIntoChunks(arr, chunkSize) {
const res = [];
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize);
res.push(chunk);
}
return res;
}
useEffect(() => {
if (error) {
setIsLoading(true);
setError(error.message);
setIsLoading(false);
}
}, [error]);
const SeeMore = ({onPress}) => {
return (
<TouchableOpacity onPress={onPress}>
<Text
size={15}
mr="sm"
color={dark ? 'primary' : colors.text}
style={styles.letSpacing}>
Tout Voir
</Text>
</TouchableOpacity>
);
};
const renderHeader = () => {
if (slidesLoading) {
return (
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
paddingHorizontal: theme.space.sm,
paddingTop: theme.space.sm,
height: HEIGHT / 4.8,
justifyContent: 'center',
alignItems: 'center',
width: WIDTH,
}}>
<ActivityIndicator color={theme.color.primary} size={24} />
</ScrollView>
);
}
return (
<>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
paddingHorizontal: theme.space.sm,
paddingTop: theme.space.sm,
height: HEIGHT / 4.8,
}}>
{slidesData.explorer_slides.map((slide) => {
const type = slide.item.__typename;
return (
<TouchableOpacity
key={slide.id}
onPress={() =>
navigation.navigate(
type === 'Flux'
? 'SingleFlux'
: type === 'FluxGroup'
? 'MultipleFlux'
: 'FluxCategory',
{
headerTitle: slide.item.name,
headerItem: slide.item,
itemId: slide.item.id,
headerText:
slide.item.__typename !== 'FluxCategory'
? slide.item.description
: null,
},
)
}>
<Box
mx="xs"
bg="primary"
w={WIDTH - 120}
h={150}
radius="sm"
align="center"
justify="center"
overflow="hidden">
<Image
source={{uri: slide.image.uri}}
style={styles.imgCat}
resizeMode="cover"
/>
</Box>
</TouchableOpacity>
);
})}
</ScrollView>
<Box mt="md" h={1} w={WIDTH} bg={dark ? 'grey' : 'lightBorder'} />
</>
);
};
const renderItem = ({item, index}) => {
return (
<Box key={item - index} mb={8}>
{item.map((section, index) => {
const multiple = section.__typename === 'FluxGroup';
const subscribed = section.subscribed;
return (
<TouchableOpacity
key={section.id}
onPress={() =>
!multiple
? navigation.navigate('SingleFlux', {
headerTitle: section.name,
itemId: section.id,
headerItem: section,
subscribed: subscribed,
itemExist: exists(section),
})
: navigation.navigate('MultipleFlux', {
headerTitle: section.name,
itemId: section.id,
headerItem: section,
subscribed: subscribed,
itemExist: exists(section),
})
}>
<SubscribeItem
flux={section}
id={section.id}
channel={section.name}
title={
section.description
? section.description
: `Toutes les actualités sur ${section.name}`
}
icon={section.image?.uri ? `${section.image?.uri}` : null}
custom={section.customChannel}
pushNumber={section.frequency_numbers_all}
multiple={multiple}
button={
<>
{/* <ToggleIcon
favorite={exists(section)}
onPress={() =>
exists(section)
? handleRemoveFavorite(section)
: handleAddFavorite(section)
}
/> */}
<ToggleIcon
favorite={favExists(section)}
onPress={() =>
favExists(section)
? handleRemoveFavorite(section)
: handleAddFavorite(section)
}
/>
</>
}
/>
</TouchableOpacity>
);
})}
</Box>
);
};
const renderCategories = () => {
if (!explorerData) {
return (
<Box py="sm">
<Text mb="sm" center color="lightGrey">
Catégories en chargement
</Text>
<Loading />
</Box>
);
}
if (explorerData) {
return explorerData.explorer_categories.map((section) => {
const sectionData = sliceIntoChunks(section.related, 3);
return (
<>
<Box
w={WIDTH}
key={section.id}
dir="row"
justify="between"
align="center">
<Text
size="xl"
pt="sm"
pb="2xs"
ml="sm"
color={dark ? 'white' : 'black'}
style={styles.header}>
{section.name}
</Text>
<SeeMore
onPress={() =>
navigation.navigate('FluxCategory', {
headerTitle: section.name,
headerItem: section,
itemId: section.id,
headerText: null,
})
}
/>
</Box>
<Box>
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.contentContainerStyle}
data={section ? sectionData : []}
renderItem={renderItem}
extraData={favoritesData}
keyExtractor={(item, index) => item + index}
onEndReachedThreshold={0}
/>
<Box h={1} bg={dark ? 'grey' : 'lightBorder'} mb="sm" />
</Box>
</>
);
});
}
};
if (error) {
return (
<Box f={1} justify="center" align="center">
<Box mb="xs">
<ErrorIcon
name="cloud-offline-outline"
color={dark ? theme.color.lightGrey : 'grey'}
size={32}
/>
</Box>
<Text
size="md"
center
color={dark ? 'lightGrey' : 'grey'}
style={styles.letSpacing}>
Une erreur s'est produite
</Text>
<Text
size="sm"
color={dark ? 'lightGrey' : 'grey'}
style={styles.letSpacing}>
Réessayez plus tard
</Text>
<TouchableOpacity onPress={() => refetch()}>
<Box mt="sm">
<RefreshIcon name="refresh" size={24} color={theme.color.primary} />
</Box>
</TouchableOpacity>
</Box>
);
}
if (isLoading) {
return <Loading />;
}
return (
<SafeAreaView
style={[styles.container, {backgroundColor: colors.background}]}>
<ScrollView showsVerticalScrollIndicator={false}>
<Box>{renderHeader()}</Box>
{renderCategories()}
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
width: WIDTH,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
searchBar: {
width: WIDTH,
backgroundColor: theme.color.secondary,
borderBottomColor: theme.color.secondary,
borderTopColor: theme.color.secondary,
},
inputBar: {
backgroundColor: theme.color.black,
borderRadius: theme.space.md,
},
header: {
fontFamily: 'System',
fontWeight: '700',
letterSpacing: 0,
},
icon: {
width: 25,
height: 25,
borderRadius: 6,
backgroundColor: theme.color.primary,
overflow: 'hidden',
},
iconNull: {
width: 25,
height: 25,
borderRadius: 6,
backgroundColor: theme.color.primary,
overflow: 'hidden',
},
imgCat: {
width: '100%',
height: 150,
},
letSpacing: {
letterSpacing: 0,
},
});
export default ExplorerScreen;
Am I missing something ? Or Am I doing it totally wrong haha ?
If you need more info on my code, don't hesitate to ask :)

Try this. The UI will get refreshed as soon as you delete the item or add item:
await unsubscribeFromFlux({
variables: {
id: parseInt(flux.id),
},
refetchQueries: GET_EXPLORER_SLIDES_QUERY
});

Well turns out, you can't persist a reactive variable! So I'm just refetching queries and updating my cache after a mutation :) All good now ! Thank you !

Related

React Native: Camera from "expo-camera" stop running when face is not ever detected

I am newer for using react-native, and wanna try to create a camera with filter. I'm blocked in step to recognize face. Have success to draw rectangle when face detected, but the problem is once it goes out of detection. The camera stop running as it fixes on the last real-time capture
Here is my code:
import { useState, useEffect, useRef } from 'react'
import { Camera } from 'expo-camera'
import * as MediaLibrary from 'expo-media-library'
import { Text, StyleSheet, View, TouchableOpacity } from 'react-native'
import Button from './Button'
import { Ionicons } from '#expo/vector-icons'
import * as FaceDetector from 'expo-face-detector'
export default function PCamera() {
const cameraRef = useRef(undefined)
const [faceDetected, setFaceDetected] = useState([])
const [lastImage, setImage] = useState(undefined)
const [hasUsePermssion, setUsePermission] = useState(false)
const [type, switchToType] = useState(Camera.Constants.Type.front)
const takePicture = async () => {
if (cameraRef) {
try {
const options = {
quality: 1,
base64: true,
exif: false,
}
const data = await cameraRef.current.takePictureAsync(options)
setImage(data.uri)
console.log(data)
} catch (err) {
console.error(err)
}
}
}
const swithMode = () => {
switchToType(
type === Camera.Constants.Type.front
? Camera.Constants.Type.back
: Camera.Constants.Type.front
)
}
const handleFacesDetected = ({ faces }) => {
setFaceDetected(faces)
}
useEffect(() => {
;(async () => {
const { status } = await Camera.requestCameraPermissionsAsync()
if (status === 'granted') {
setUsePermission(true)
}
})()
}, [])
if (hasUsePermssion === null) {
return <View />
}
if (hasUsePermssion === false) {
return <Text>No access to camera</Text>
}
return (
<View style={styles.cameraContainer}>
<View style={styles.overlay}>
<Camera
ref={cameraRef}
style={styles.camera}
type={type}
onFacesDetected={handleFacesDetected}
faceDetectorSettings={{
mode: FaceDetector.FaceDetectorMode.fast,
detectLandmarks: FaceDetector.FaceDetectorLandmarks.all,
runClassifications:
FaceDetector.FaceDetectorClassifications.none,
minDetectionInterval: 100,
tracking: true,
}}
>
{faceDetected.length > 0 &&
faceDetected.map((face) => (
<View
key={face.faceID}
style={{
position: 'absolute',
borderWidth: 2,
borderColor: 'red',
left: face.bounds.origin.x,
top: face.bounds.origin.y,
width: face.bounds.size.width,
height: face.bounds.size.height,
}}
/>
))}
</Camera>
</View>
<View style={styles.optionsContainer}>
<View>
<TouchableOpacity onPress={swithMode}>
<Text>
<Ionicons
name="camera-reverse-outline"
size={24}
color="black"
/>
</Text>
</TouchableOpacity>
</View>
<Button
icon="camera"
title="Take Photo"
onPress={takePicture}
style={styles.button}
/>
<View>
<Text>...</Text>
</View>
</View>
</View>
)}
const styles = StyleSheet.create({
cameraContainer: {flex: 1,
},
overlay: {
flex: 6,
borderBottomStartRadius: 75,
borderBottomEndRadius: 75,
overflow: 'hidden',
},
camera: {
flex: 1,
},
optionsContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
},
})
N.B: Don't take care of the Button, it's a custom component and works well

Drawer.Screen not showing on android but works perfectly on iOS

I'm not sure what happened, but I had to clean/rebuild gradle from a version issue with react-native-screens, and when I finally got everything back and working, the screens don't show on Android, but they work exactly as they did before with the issue. I'm not even sure how or where to look. I know it's the Drawer Navigator because if I pass a view instead it will show. Keep in mind - before I had the gradle issue, EVERYTHING worked - which is why I'm so stumped.
Here is my code:
App.tsx
import 'react-native-gesture-handler';
import React, { useEffect, useRef, useState } from 'react';
import { NavigationContainer, DefaultTheme, DarkTheme, LinkingOptions, NavigatorScreenParams, useNavigationContainerRef } from '#react-navigation/native';
import { createNativeStackNavigator, NativeStackHeaderProps } from '#react-navigation/native-stack';
import { Platform, StatusBar, View } from 'react-native';
import { enableScreens } from 'react-native-screens';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import Orientation from 'react-native-orientation-locker';
import OneSignal from 'react-native-onesignal';
import analytics from '#react-native-firebase/analytics';
import {
StyleSheet
} from 'react-native';
import MenuButton from './src/components/layout/MenuButton';
// Import pages and components
import { colors } from './src/styles/colorPalette';
import MainHeader from './src/components/headers/MainHeader';
import DrawerNavigator from './src/navigators/DrawerNavigator';
import ThemeContextProvider, { useTheme } from './src/contexts/ThemeContext';
import AuthContextProvider from './src/contexts/AuthContext'
import { useAuth } from './src/contexts/AuthContext';
import LoginRegister from './src/components/LoginRegister';
import EmailPhoneCollector from './src/components/EmailPhoneCollector';
import Search from './src/components/Search';
import SearchHeader from './src/components/headers/SearchHeader'
import DetailView from './src/components/DetailView';
import FullScreenLoader from './src/components/FullScreenLoading';
// Import theme and styling files
import { testAppDefaultTheme, testAppDefaultDarkTheme } from './src/styles/theme';
import Home from './src/pages/home';
// Globals (fonts, etc)
const sequel100Wide = Platform.OS == 'ios' ? 'Sequel100Wide-65' : 'Sequel100Wide-85'
type DrawerParamsList = {
Home: undefined
Videos: undefined
News: undefined
Podcasts: undefined
}
type RootStackParamList = {
Drawer: NavigatorScreenParams<DrawerParamsList>
DetailView: undefined
Search: undefined
config?: undefined
}
const Stack = createNativeStackNavigator()
enableScreens()
export default function App(props: any) {
// To override login functionality
const bypassLogin = false
const clearOnRestart = false
const CustomApp = () => {
const { clearID, logout, user, emailSMSId, timeExceeded } = useAuth()
const { darkMode, showNavBar } = useTheme()
const navigationRef = useNavigationContainerRef()
const routeNameRef = useRef<string | undefined>('')
// Where we display pre-prompt on login and iOS platform
const checkIosSoftPrompt = async () => {
const deviceState = await OneSignal.getDeviceState()
if (!deviceState?.isSubscribed) {
OneSignal.addTrigger('ios_prompt', 'true')
}
}
const linking: LinkingOptions<RootStackParamList> = {
prefixes: ['testApp://', 'https://testApp.com', 'https://*.testApp.com'],
config: {
screens: {
Drawer: {
initialRouteName: 'Home',
screens: {
Videos: 'tv',
News: 'news',
Podcasts: 'podcasts',
Home: ''
}
},
DetailView: {
path: ':category?/:type/:slug',
},
Search: {
path: 'search',
}
}
},
}
const HeaderComponent = (props: any) => <MainHeader {...props} />
useEffect(() => {
if (bypassLogin && user) logout()
/* if ((bypassLogin || (user || emailSMSId)) && Platform.OS == 'ios') {
checkIosSoftPrompt()
} */
// More aggressive targeting for Pre-Prompt message
if (Platform.OS === 'ios' && (emailSMSId)) checkIosSoftPrompt();
}, [bypassLogin, user, emailSMSId])
useEffect(() => {
if (clearOnRestart) clearID()
}, [])
const checkRoutes = async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name ?? '';
if (previousRouteName !== currentRouteName) {
await analytics().logScreenView({ screen_name: currentRouteName });
}
// Save the current route name for later comparison
routeNameRef.current = currentRouteName;
}
const setRefs = () => {
routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name;
}
return(
<SafeAreaProvider>
<StatusBar translucent barStyle={darkMode ? 'light-content' : 'dark-content'} backgroundColor="transparent" />
<NavigationContainer
ref={navigationRef}
onReady={ setRefs }
onStateChange={ checkRoutes }
theme={darkMode ? testAppDefaultDarkTheme : testAppDefaultTheme}
linking={linking}
fallback={<FullScreenLoader message='Loading...'/>}
>
{ bypassLogin || !!user || emailSMSId || (timeExceeded != null && !timeExceeded ) ?
<Stack.Navigator
screenOptions={{ header: HeaderComponent, contentStyle:{ backgroundColor: darkMode ? colors.background.darkHeaderBg : colors.background.lightSubdued } }}
>
<Stack.Screen name="Drawer" component={DrawerNavigator} options={{ headerShown: false }} />
{ /* Common screens outside flow of Drawer Navigator */}
<Stack.Screen name="Search" component={Search}
options={{
header: SearchHeader,
headerShown: false,
animation: 'fade_from_bottom',
headerTitle: '',
headerStyle: {
backgroundColor: darkMode ? colors.primary.blue7 : '#fff',
},
contentStyle: {
backgroundColor: darkMode ? colors.primary.blue7 : '#fff',
}
}}
/>
<Stack.Screen name="DetailView" component={DetailView}
options={{
presentation: 'containedModal',
headerShown: false,
contentStyle: {
backgroundColor: darkMode
? colors.primary.blue7
: '#fff',
},
orientation:
Platform.OS === 'ios' ? 'portrait_up' : undefined,
headerBackButtonMenuEnabled: false,
headerBackVisible: false,
headerTitle: ''
}}
/>
</Stack.Navigator>
:
<EmailPhoneCollector />
}
</NavigationContainer>
</SafeAreaProvider>
)
}
// Main app useEffect hook
useEffect(() => {
// Lock orientation with Orientation (rather than React Navigation)
Orientation.lockToPortrait()
// OneSignal initializer
OneSignal.setLogLevel(6, 0);
OneSignal.setAppId("99a9aeab-f82e-49e8-bc40-8f471c7bf1f9");
/* //Prompt for push on iOS
OneSignal.promptForPushNotificationsWithUserResponse((response: any) => { // Replace this for pre-prompt flow
console.log("Prompt response:", response);
}); */
//Method for handling notifications received while app in foreground
OneSignal.setNotificationWillShowInForegroundHandler(notificationReceivedEvent => {
console.log("OneSignal: notification will show in foreground:", notificationReceivedEvent);
let notification = notificationReceivedEvent.getNotification();
console.log("notification: ", notification);
const data = notification.additionalData
console.log("additionalData: ", data);
// Complete with null means don't show a notification.
notificationReceivedEvent.complete(notification);
});
//Method for handling notifications opened
OneSignal.setNotificationOpenedHandler((notification) => {
// console.log("OneSignal: notification opened:", notification);
});
}, [])
return (
<AuthContextProvider>
<ThemeContextProvider>
<CustomApp />
</ThemeContextProvider>
</AuthContextProvider>
);
}
const styles = StyleSheet.create({
drawerIcon: {
width: 30,
height: 30,
}
})
DrawerNavigator:
import React, { useEffect, useState } from 'react';
import { Linking, View, TouchableOpacity, Platform } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { SafeAreaView } from 'react-native-safe-area-context';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import { Divider, Switch } from 'react-native-elements';
import { DrawerContentScrollView, DrawerItem, DrawerItemList } from '#react-navigation/drawer';
import { colors } from '../../styles/colorPalette';
import { useTheme } from '../../contexts/ThemeContext';
import HomeLogo from './HomeLogo';
import { Button } from 'react-native-elements/dist/buttons/Button';
import Text from '../TextComponents'
// Contexts
import { useAuth } from '../../contexts/AuthContext'
import VersionCheck from 'react-native-version-check';
// Globals (fonts, etc)
const sequel100Wide6585 = Platform.OS == 'ios' ? 'Sequel100Wide-65' : 'Sequel100Wide-85'
export default function DrawerWrapper(props: any) {
const darkMode = useTheme().darkMode;
const { navigation, route, state } = props;
const { appInfo, user, logout } = useAuth();
const goToStore = async () => {
if (Platform.OS == 'ios') {
VersionCheck.getAppStoreUrl({ appID: "1609502829" })
.then(res => {
if (res) {
Linking.canOpenURL(res)
.then(supported => {
console.log('the link: ', res)
supported && Linking.openURL(res)
},
(err) => console.log('Error opening link: ', err)
)
}
})
}
else if (Platform.OS == 'android') {
VersionCheck.getPlayStoreUrl({ packageName: "com.testAppmobile.testApp" })
.then(res => {
if (res) {
Linking.canOpenURL(res)
.then(supported => {
console.log('the link: ', res)
supported && Linking.openURL(res)
},
(err) => console.log('Error opening link: ', err)
)
}
})
}
}
return (
<SafeAreaView style={{flex: 1}} edges={[]}>
<View style={{ flexDirection: "row", padding: 20, alignItems: 'center', alignContent: 'center', marginTop: Platform.OS == 'android' ? 20 : 0 }}>
<TouchableOpacity onPress={() => navigation.closeDrawer()}>
<Icon name="md-close" size={40} color={darkMode ? colors.primary.blue2 : 'black'}/>
</TouchableOpacity>
<HomeLogo />
<TouchableOpacity style={{flexGrow: 1, display: 'none'}}>
<FontAwesome style={{textAlign: 'right'}} name="cog" size={40} color={darkMode ? colors.primary.blue2 : 'black'} />
</TouchableOpacity>
</View>
{ user && <View style={{flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', paddingHorizontal: 20, paddingBottom: 10}}>
<Text style={{marginRight: 20}}>{user?.username ?? user?.name ?? user?.email}</Text>
<Button title="Logout" titleStyle={{fontSize: 12, textTransform: 'uppercase'}} style={{backgroundColor: 'red'}} onPress={() => logout()}/>
</View>}
<Divider />
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} labelStyle={{fontFamily: sequel100Wide6585}}/>
<DrawerItem
onPress={() => Linking.openURL('https://testApp.com')}
activeBackgroundColor={darkMode ? colors.primary.blue5 : colors.primary.blue1} {...props}
label="testApp.com"
labelStyle={{fontFamily: sequel100Wide6585,
fontWeight: 'normal',
fontSize: 13,
paddingVertical: 15,
color: darkMode ? colors.primary.blue2 : '#000'}} icon={({focused, size, color}) => <FontAwesome size={30} style={{color: darkMode ? '#98D4FF' : "#000", marginLeft: -5}} name="external-link-square"/>}
/>
</DrawerContentScrollView>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
{ !!appInfo?.appVersion && <Text variant="h5" style={{ color: darkMode ? '#fff' : '#000', paddingLeft: 10 }}>{`v${appInfo.appVersion}`}</Text> }
{ !!appInfo?.needsUpdate && <Button onPress={goToStore} buttonStyle={{borderRadius: 10, backgroundColor: 'green', marginLeft: 10}} title={<Text variant="h5" style={{color: '#fff'}}>Update Available</Text>} />}
</View>
</SafeAreaView>
)
}
My Home Stack:
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { ActivityIndicator, FlatList, Platform, RefreshControl, RefreshControlProps, StyleSheet, TouchableOpacity, View } from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { useHeaderHeight } from '#react-navigation/elements';
import PostCard from '../../components/PostCard';
import FullScreenLoader from '../../components/FullScreenLoading';
import { colors } from '../../styles/colorPalette';
import { useTheme } from '../../contexts/ThemeContext';
import LiveVideoCard from '../../components/LiveVideoCard';
import HorizontalCarousel from '../../components/HorizontalCarousel';
import { KeyboardAwareFlatList, KeyboardAwareFlatListProps } from 'react-native-keyboard-aware-scroll-view';
import FooterStack from '../../components/layout/FooterStack';
import PersonDetails from '../../components/PersonDetails';
import Text from '../../components/TextComponents';
import NoResults from '../../components/NoResults';
import Animated, { enableLayoutAnimations, FadeIn, Layout } from 'react-native-reanimated';
const Home = (props: any) => {
const { navigation } = props;
const darkMode = useTheme().darkMode;
const headerHeight = useHeaderHeight();
const Stack = createNativeStackNavigator();
const styles = StyleSheet.create({
button: {
backgroundColor: 'transparent',
borderRadius: 50,
flex: 1,
width: 120,
height: 40,
marginHorizontal: 10,
},
container: {
flexGrow: 1,
flex: 1,
},
image: {
height: 300,
resizeMode: 'contain',
},
logo: {
height: 100,
width: '100%',
resizeMode: 'contain',
},
posts: {
borderStyle: 'solid',
borderWidth: 5,
borderColor: 'red',
},
subtitle: {
fontSize: 12,
marginTop: 3,
color: '#777777',
flex: 1,
},
title: {
fontSize: 24,
fontWeight: 'bold',
flex: 1,
},
videoContainer: {
position: 'relative',
height: 300,
marginVertical: 25,
flex: 1,
},
watchMore: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 20,
borderTopWidth: 1,
borderBottomWidth: 1,
borderStyle: 'solid',
borderColor: 'rgba(0,0,0,0.25)',
},
});
const HomeHeader = React.memo(() => {
const darkMode = useTheme().darkMode
return (
<>
<LiveVideoCard />
<View>
<View style={[styles.watchMore, { backgroundColor: darkMode ? colors.background.darkBg : '#fff' }]}>
<Text style={{color: darkMode ? '#fff' : '#000', fontSize: 15, fontWeight: 'bold'}}>
Watch
</Text>
<TouchableOpacity onPress={() => navigation.navigate('Videos')}>
<Text style={{color: darkMode ? colors.primary.blue2 : colors.primary.blueMain, fontWeight: 'bold', fontSize: 14}}>
Watch More
</Text>
</TouchableOpacity>
</View>
<HorizontalCarousel />
</View>
</>
)
}, (prev, next) => {
return true
})
const FooterItem = React.memo(() => {
const darkMode = useTheme().darkMode
return(
<View style={{paddingBottom: 100}}>
<Text variant="h6" style={{alignSelf: 'center', color: darkMode ? '#fff' : '#000'}}>Loading more posts...</Text>
<ActivityIndicator size={'large'} color={darkMode ? '#fff' : '#777'} style={{ paddingVertical: 20 }} />
</View>
)}, () => true)
const renderItem = useCallback((props: any) => {
return (
<PostCard data={props.item.node} categoryOverlay="Home" index={props.index} navigation={navigation} />
)
}, [])
const CustomRefreshControl = (props: RefreshControlProps) => {
const darkMode = useTheme().darkMode
return <RefreshControl
progressViewOffset={headerHeight}
refreshing={props.refreshing}
onRefresh={props.onRefresh}
tintColor={darkMode ? '#fff' : '#000'}
titleColor={darkMode ? '#fff' : '#000'}
/>
}
const HomeComponent = useCallback(() => {
const [data, setData] = useState([]);
const flatlistRef = useRef<FlatList>(null)
const onEndReachedCalledDuringMomentum = useRef(false)
const [loading, setLoading] = useState(true);
const [pageNum, setPageNum] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const controller = new AbortController()
const fetchData = async (newPageNum?: number) => {
if (newPageNum !== 0 && newPageNum == pageNum) return console.log("They're the same")
const timer = setTimeout(() => {
controller?.abort()
}, 10000)
try {
// Here is where we try to fetch data
const videos = await fetch(
`https://home.testApp.com/m/shows/latest?page=${newPageNum ?? pageNum}`,
{
method: 'GET',
mode: 'cors',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'testApp/2.0'
},
signal: controller.signal
}
);
const json = await videos
.json()
.catch((e: any) =>
console.log('this is an error in the json output: ', e),
);
clearTimeout(timer)
setData(data.concat(json.nodes));
if (!!newPageNum) setPageNum(newPageNum)
} catch (e) {
// Manage the errors here
console.log('Error returning from testApp videos: ', e);
} finally {
if (newPageNum === 0 || !!loading) setLoading(false)
if (!!refreshing) setRefreshing(false)
controller?.abort()
}
};
const keyExtractor = (item: any, idx: number) => {
return `${item.node.nid}-${idx}`
};
const handleLoadMore = async (props: any) => {
if (onEndReachedCalledDuringMomentum.current === true) return console.log('momentum blocked')
fetchData(pageNum + 1)
onEndReachedCalledDuringMomentum.current = true
}
const handleRefresh = async () => {
setRefreshing(true);
setLoading(true);
setData([]);
fetchData(0);
setRefreshing(false)
}
const _onMomentumScrollBegin = () => {
onEndReachedCalledDuringMomentum.current = false
}
useEffect(() => {
// This is where we fetch the data from the website
if (loading) fetchData();
// Pass ref to navigation object for scrollTo logic
navigation.setOptions({
homeScrollRef: flatlistRef
})
return () => controller?.abort()
}, []);
if (!!loading) return <FullScreenLoader message={'Loading Home posts'} />
return (
<FlatList
ref={flatlistRef}
contentContainerStyle={{marginTop: headerHeight ?? undefined }}
style={{flex: 1 }}
scrollIndicatorInsets={{ right: 1 }}
showsVerticalScrollIndicator={false}
initialNumToRender={5}
maxToRenderPerBatch={15}
windowSize={15}
data={data}
keyExtractor={keyExtractor}
ListEmptyComponent={<NoResults />}
ListHeaderComponent={HomeHeader}
ListFooterComponent={FooterItem}
renderItem={renderItem}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
onMomentumScrollBegin={_onMomentumScrollBegin}
scrollEventThrottle={16}
refreshControl={<CustomRefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>}
/>
)
}, [])
return (
<Stack.Navigator screenOptions={{ orientation: Platform.OS === 'ios' ? 'portrait_up' : 'portrait_up', headerShown: false, contentStyle: { backgroundColor: darkMode ? colors.background.darkHeaderBg : colors.background.lightSubdued }}}>
<Stack.Screen name="HomeView" options={{title: 'Home' }} component={HomeComponent} />
<Stack.Screen name="FooterLinks" component={FooterStack} />
</Stack.Navigator>
);
}
export default Home
try disable animation on android
import { enableLayoutAnimations } from "react-native-reanimated";
if (Platform.OS === 'android') {
enableLayoutAnimations(false);
}

Check the render method of 'customComponent'

I have made a Login page in React Native with React hooks along with redux thunk connect.
In that Login Page, i have import a LoginForm as customComponent and I have properly export and import that component.But still it produce Element type error.
LoginPage
import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Dimensions,
} from "react-native";
import PropsType from "prop-types";
import { connect } from "react-redux";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scrollview";
import SimpleReactValidator from "simple-react-validator";
import Img from "../../common/Img";
import Loader from "../../common/Loader";
import styles from "../../globalStyle";
import * as theme from "../../theme";
import AxiosInstance from "../../helper/axios.interceptor";
import * as globalHelper from "../../helper/globalHelper";
import { setAuth } from "../../store/auth/auth.action";
import LoginForm from "./Component/LoginForm";
const { height } = Dimensions.get("screen");
const propsType = {
authAction: PropsType.func,
};
let request = { type: "mobile" };
const validator = new SimpleReactValidator();
const SignIn = ({ navigation, authAction }) => {
const [errors, setErrors] = useState({});
const [state, setState] = useState({ loader: false }),
updateState = (key, value) => {
setState((preState) => ({
...preState,
[key]: value,
}));
},
openLoader = (val) => {
updateState("loader", val);
};
const submit = async () => {
try {
openLoader(true);
let body = {
type: "mobile",
userName: request.userName,
password: request.password,
};
console.log("body", body);
const response = await AxiosInstance.post("auth/login", body);
console.log("loginresponse...", response);
if (response.status) {
await globalHelper.setAsyncStore("user", response);
await globalHelper.setAsyncStore(
"userpermission",
response.data.permissionJson
);
authAction(response);
navigation.replace("AppNavigation");
}
openLoader(false);
} catch (err) {
openLoader(false);
console.log("login error", err);
}
};
//
const ForgotPassword = () => (
<TouchableOpacity
onPress={() => {
validator.hideMessages();
setErrors("");
navigation.push("ForgotPassword");
}}
>
<Text style={[localStyle.forgotText]}>Forgot Password?</Text>
</TouchableOpacity>
);
return (
<>
<KeyboardAwareScrollView>
<View style={[styles.flexCenter, { height }]}>
<Img
src={require("../../assets/logo/logoNew.png")}
style={{ width: 237, height: 250, marginBottom: -20 }}
resizeMode="contain"
/>
<View style={localStyle.authButton}>
<LoginForm
validator={validator}
onTextChanging={(data) => {
request = { ...request, ...data };
}}
onSubmit={() => {
submit();
}}
errors={errors}
setErrors={setErrors}
/>
</View>
<View style={[styles.flexCenter]}>
<ForgotPassword />
</View>
</View>
</KeyboardAwareScrollView>
{state.loader && <Loader />}
</>
);
};
const { color } = theme;
const localStyle = StyleSheet.create({
authButton: {
width: "80%",
borderRadius: 5,
},
forgotText: {
marginTop: 20,
color: color.hashTextColor,
},
});
SignIn.propsType = propsType;
const mapDispatchToProps = {
authAction: setAuth,
};
export default connect(null, mapDispatchToProps)(SignIn);
and the customComponent LoginForm as
import React, { useState, useEffect, useRef } from "react";
import {
View,
Text,
TouchableOpacity,
ScrollView,
StyleSheet,
} from "react-native";
import { Form, Icon } from "native-base";
import PropsType from "prop-types";
import { has } from "lodash";
import RegularInput from "../../../common/Input";
import styles from "../../../globalStyle";
import AuthButton from "../../../common/Button/AuthButton";
const propsType = {
onTextChanging: PropsType.func.isRequired,
onSubmit: PropsType.func.isRequired,
};
// const validator = new SimpleReactValidator();
const LoginForm = ({
onTextChanging,
onSubmit,
validator,
errors,
setErrors,
}) => {
const [model, setModel] = useState({ focus: "username", secured: true });
const isValid = (key) => !!(has(errors, key) && errors[key]);
const [state, setState] = useState({
userName: "",
password: "",
});
const secondTextInput = useRef(null);
useEffect(() => {
onTextChanging(state);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state]);
const updateState = (key, value) => {
setState((preState) => ({
...preState,
[key]: value,
}));
};
const submit = () => {
if (validator.allValid()) {
onSubmit();
} else {
console.log("getErrorMessages");
setErrors(validator.getErrorMessages());
validator.showMessages();
}
};
return (
<ScrollView keyboardShouldPersistTaps="handled">
<Form style={[styles.top50, { width: "100%" }]}>
<View style={{ width: "100%", paddingBottom: 15 }}>
<RegularInput
title="Email"
value={state.userName}
attri="userName"
placeHolder={"Email"}
keyboardType="ascii-capable"
updateStateFunc={updateState}
error={isValid("Username")}
isFocus={model.focus === "username"}
onFocusFun={() => setModel({ focus: "username" })}
otherProps={{
onSubmitEditing: () => {
console.log(secondTextInput);
secondTextInput.current.focus();
},
blurOnSubmit: false,
}}
/>
</View>
{!!validator.message("Email", state.userName, "email") && (
<Text style={[styles.error]}>
{validator.message("Email", state.userName, "email")}
</Text>
)}
<View
style={[
styles.flexCenter,
styles.flexRow,
localStyle.absoluteContainer,
]}
>
<RegularInput
reff={secondTextInput}
title="Password"
value={state.password}
attri="password"
placeHolder={"Password"}
updateStateFunc={updateState}
dataDetectorTypes="phoneNumber"
secureTextEntry={!state.secured}
error={isValid("Password")}
isFocus={model.focus === "password"}
onFocusFun={() => setModel({ focus: "password" })}
/>
<TouchableOpacity
style={localStyle.eyeIcon}
onPress={() => {
updateState("secured", !state.secured);
}}
>
{state.secured && (
<Icon style={{ fontSize: 16 }} name="eye" type="Entypo" />
)}
{!state.secured && (
<Icon
style={{ fontSize: 16 }}
name="eye-with-line"
type="Entypo"
/>
)}
</TouchableOpacity>
</View>
{!!validator.message("Password", state.password, "required") && (
<Text style={[styles.error]}>
{validator.message("Password", state.password, "required")}
</Text>
)}
<View style={[styles.flexCenter, localStyle.authButton]}>
<AuthButton title="LOGIN" onPress={() => submit()} />
</View>
</Form>
</ScrollView>
);
};
const localStyle = StyleSheet.create({
authButton: {
marginTop: 10,
borderRadius: 5,
},
inputStyle: {
height: 40,
borderColor: "gray",
borderBottomWidth: 1,
},
absoluteContainer: {
position: "relative",
overflow: "hidden",
width: "100%",
},
eyeIcon: {
position: "absolute",
// right: 0,
width: 25,
height: 25,
elevation: 999,
zIndex: 999,
top: 42,
right: 5,
},
});
LoginForm.propsType = propsType;
export default LoginForm;
I have tried
import LoginForm from "./Component/LoginForm";
as
import {LoginForm} from "./Component/LoginForm";
It produce check the render method of SignIn
and also use connect in LoginForm,
export default connect(null, null)(LoginForm);
but it gives same error. I don't know what mistake i have made.unable to find any cause. Thanks in advance
Element type Error

How to Open left side drawer and right side drawer in react native project

I wrote the below code to make multiple side drawer but unluckily I am not able.
Here open the left side drawer easily but the right-side drawer is not open. for Analyze purpose header.js file when I used here onPress={() => navigation.openDrawer()} then open left side drawer and I also used in second drawer element onPress={() => navigation.openDrawer()} also open left ride drawer but I want to open right side drawer.
What should I do?
app.js file:
import * as React from 'react';
import {View, Button,StyleSheet, Alert, Text, ActivityIndicator, Image} from 'react-native';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import {createDrawerNavigator} from '#react-navigation/drawer';
import DrawerContent from './screens/common/DrawerContent';
import SearchDrawer from './screens/common/SearchDrawer';
import Registration from './screens/authentication/Registration';
import Login from './screens/authentication/Login';
import ForgotPassword from './screens/authentication/ForgotPassword';
import ChangePassword from './screens/authentication/ChangePassword';
import Dashboard from './screens/user/Dashboard';
import Notifications from './screens/user/Notifications';
import ProfileStep from './screens/user';
import Shipment from './screens/user/shipment';
// import SplashScreen from 'react-native-splash-screen';
// import {LogBox} from 'react-native';
import {Provider} from 'react-redux';
import AsyncStorage from '#react-native-community/async-storage';
import {store, getAsyncStorage} from './redux/store';
import messaging from '#react-native-firebase/messaging';
const Stack = createStackNavigator();
const DrawerR = createDrawerNavigator();
const DrawerL = createDrawerNavigator();
function User(props) {
const [userInfo, setUserInfo] = React.useState('');
React.useEffect(() => {
getUserInfo();
}, []);
const getUserInfo = async () => {
let userInfo = await AsyncStorage.getItem('userInfo');
if (userInfo) {
store.dispatch(getAsyncStorage());
userInfo = JSON.parse(userInfo);
setUserInfo(userInfo);
}
};
return (
<DrawerL.Navigator
drawerContent={(props) => (
<DrawerContent {...props} size="22" data={userInfo} />
)}
// drawerPosition="right"
// drawerContent={(props) => (
// <SearchDrawer {...props} size="22" />
// )}
>
{props.route.params !== undefined && props.route.params.completed ? (
<>
<DrawerL.Screen name="Dashboard" component={Dashboard} />
<DrawerL.Screen name="ProfileStep" component={ProfileStep} />
</>
) : (
<>
<DrawerL.Screen name="ProfileStep" component={ProfileStep} />
<DrawerL.Screen name="Dashboard" component={Dashboard} />
</>
)}
<DrawerL.Screen name="Shipment" component={Shipment} />
</DrawerL.Navigator>
);
}
function User1(props){
const [userInfo1, setUserInfo1] = React.useState('')
React.useEffect(() => {
getUserInfo1();
}, []);
const getUserInfo1 = async () => {
let userInfo1 = await AsyncStorage.getItem('userInfo');
if (userInfo1) {
store.dispatch(getAsyncStorage());
userInfo1 = JSON.parse(userInfo1);
setUserInfo1(userInfo1);
}
};
return (
<DrawerR.Navigator
drawerPosition="right"
drawerContent={(props) => (
<SearchDrawer {...props} size="22" data={userInfo1}/>
)}
>
</DrawerR.Navigator>
);
}
function App() {
React.useEffect(() => {
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage));
});
return unsubscribe;
}, []);
return (
<Provider store={store}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
<Stack.Screen name="ForgotPassword" component={ForgotPassword} />
<Stack.Screen name="ChangePassword" component={ChangePassword} />
<Stack.Screen name="User" component={User} />
<Stack.Screen name="User1" component={User1} />
<Stack.Screen name="Notifications" component={Notifications} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}
export default App;
DrawerContent.js
import React,{useEffect} from 'react';
import {View, StyleSheet, Alert, Text, ActivityIndicator, Image} from 'react-native';
import {
Avatar,
Title,
Caption,
Drawer,
TouchableRipple,
Divider,
Switch,
} from 'react-native-paper';
import {DrawerContentScrollView, DrawerItem} from '#react-navigation/drawer';
import AsyncStorage from '#react-native-community/async-storage';
import {connect} from 'react-redux';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {setTheme} from './../../redux/actions/config';
import {getThemeColors} from '../../global/themes';
import {API_URL} from './../../global/config';
import { Linking } from 'react-native';
// import {bindActionCreators} from 'redux';
var pkg = require('./../../package.json');
function DrawerContent(props) {
const [loading, setLoading] = React.useState(false);
const [profileStatus, setProfileStatus] = React.useState({});
const [whitelabel, setIfWhiteLabel] = React.useState('')
// const [isDarkMode, setIsDarkMode] = React.useState(false);
let userInfo = props.data;
const logout = () => {
Alert.alert(
'Are you sure?',
'You want to Logout',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Yes',
onPress: () => {
setLoading(true);
console.log(`${API_URL}disableDevice`);
fetch(`${API_URL}disableDevice`, {
method: 'post',
body: JSON.stringify({user_id: userInfo.id}),
})
.then((res) => res.json())
.then(async (res) => {
if (!res.status) {
setLoading(false);
alert('something went wrong!!');
} else {
try {
await AsyncStorage.removeItem('userInfo');
await AsyncStorage.removeItem('profileStatus');
props.navigation.reset({
index: 0,
routes: [{name: 'Login'}],
});
} catch (exception) {
setLoading(false);
return false;
}
}
})
.catch((e) => {
setLoading(false);
console.log(e);
});
},
},
],
{cancelable: false},
);
};
const onThemeChange = async () => {
AsyncStorage.setItem(
'themeMode',
props.theme === 'default' ? 'dark' : 'default',
);
props.onSelectTheme(props.theme === 'default' ? 'dark' : 'default');
fetch(`${API_URL}toggleDarkMode`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: userInfo.id,
dark_mode: props.theme === 'default' ? 1 : 0,
}),
})
.then((res) => res.json())
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
const {colors} = props;
React.useEffect(() => {
var profileStatus = props.profileStatus;
if (profileStatus) {
profileStatus = JSON.parse(profileStatus);
setProfileStatus(profileStatus);
}
checkIfWhiteLabel()
}, [props.profileStatus]);
useEffect(() => {
checkIfWhiteLabel()
}, [])
const checkIfWhiteLabel = async () =>{
let iftru = await AsyncStorage.getItem('whiteLabel');
if(iftru == null){
setIfWhiteLabel('false')
}else{
setIfWhiteLabel(iftru)
}
}
return (
<>
<View style={{flex: 1, backgroundColor: colors.drawerBackground}}>
<DrawerContentScrollView {...props}>
<View style={styles.drawerContent}>
<View style={styles.tinyLogoDiv}>
<Image
style={styles.tinyLogo}
source={require('../../assets/logo.png')}
/>
</View>
<TouchableRipple
style={styles.userInfoSection}
onPress={() =>
props.navigation.navigate('ProfileStep', {status: 'TRUE'})
}
rippleColor="rgba(0, 0, 0, .32)">
<View style={{flexDirection: 'row'}}>
<Avatar.Image
source={require('../../assets/avater.png')}
size={50}
/>
<View style={{marginLeft: 15, flexDirection: 'column'}}>
<Title style={styles.title(colors.textColor)}>
{profileStatus.username ?? userInfo.name}
</Title>
<Caption style={styles.caption(colors.textColor)}>
{profileStatus.email ?? userInfo.email}
</Caption>
</View>
<View
style={{
justifyContent: 'center',
marginLeft: 'auto',
paddingRight: 7,
}}>
<Icon
name="account-edit"
size={25}
color={colors.textColor}
/>
</View>
</View>
</TouchableRipple>
<Divider />
<Drawer.Section style={styles.drawerSection}>
<DrawerItem
icon={() => (
<Icon name="home" size={30} color={colors.textColor} />
)}
labelStyle={{color: colors.textColor}}
label="Home"
onPress={() => {
if (profileStatus.type || profileStatus.type === undefined) {
alert('Complete Your profile first');
} else {
props.navigation.navigate('Dashboard');
}
}}
/>
<DrawerItem
icon={() => (
<Icon
name="text-box-plus"
size={30}
color={colors.textColor}
/>
)}
label="Parcel"
labelStyle={{color: colors.textColor}}
onPress={() => {
if (profileStatus.type || profileStatus.type === undefined) {
alert('Complete Your profile first');
} else {
props.navigation.navigate('Shipment');
}
}}
/>
</Drawer.Section>
</View>
</DrawerContentScrollView>
<Drawer.Section style={styles.bottomDrawerSection}>
<DrawerItem
style={{paddingBottom: 0}}
icon={() =>
loading ? (
<ActivityIndicator size={30} color={colors.primaryColor} />
) : (
<Icon name="exit-to-app" size={30} color={colors.textColor} />
)
}
label={'Logout'}
labelStyle={{color: colors.textColor}}
onPress={() => logout()}
/>
</Drawer.Section>
<View
style={{
marginBottom: 5,
alignItems: 'center',
}}>
<Text style={{opacity: 0.8, color: colors.textColor}}>
V - {pkg.version}
</Text>
</View>
</View>
</>
);
}
const mapStateToProps = (state) => {
let imagePlaceholder = JSON.parse(state.brand);
if (imagePlaceholder) {
imagePlaceholder = imagePlaceholder.default.avatar;
} else {
imagePlaceholder = '';
}
var theme = getThemeColors(state.theme);
return {
theme: state.theme,
colors: theme,
profileStatus: state.profileStatus,
imagePlaceholder: imagePlaceholder,
};
// count: state.count,
};
export default connect(mapStateToProps, {
onSelectTheme: setTheme,
})(DrawerContent);
//
const styles = StyleSheet.create({
drawerContent: {
flex: 1,
},
userInfoSection: {
paddingLeft: 20,
paddingVertical: 10,
},
title: (color) => ({
fontSize: 16,
marginTop: 3,
fontWeight: 'bold',
color: color,
}),
caption: (color) => ({
fontSize: 12,
lineHeight: 14,
opacity: 0.8,
color: color,
marginLeft:-15,
}),
row: {
marginTop: 20,
flexDirection: 'row',
alignItems: 'center',
},
section: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 15,
},
paragraph: {
fontWeight: 'bold',
marginRight: 3,
},
drawerSection: {
marginTop: 15,
},
bottomDrawerSection: {
// marginBottom: 15,
borderTopColor: '#ccc',
borderTopWidth: 0.2,
borderBottomColor: '#ccc',
borderBottomWidth: 0.2,
},
preference: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 12,
paddingHorizontal: 16,
},
loading: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
opacity: 0.6,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
fontSize: 20,
paddingTop: 20,
fontWeight: 'bold',
},
tinyLogoDiv:{
// width:100,
},
tinyLogo: {
width: 150,
height: 50,
// alignItems:'center',
marginLeft:70
},
});
SearchDrawer.js
import React from 'react';
import { View, StyleSheet, Text, Button,TextInput } from 'react-native';
import { DrawerContentScrollView} from '#react-navigation/drawer';
import { setTheme } from './../../redux/actions/config';
import { getThemeColors } from '../../global/themes';
import { connect } from 'react-redux';
function SearchDrawer(props) {
return (
<>
<View style={{ flex: 1, backgroundColor: '#ffffff' }}>
<DrawerContentScrollView {...props}>
<View>
<Text>This is input Field</Text>
<TextInput
label='Search here'
style={styles.input}
placeholder="Search By Order Id"
/>
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
</View>
</DrawerContentScrollView>
</View>
</>
);
}
const mapStateToProps = (state) => {
let imagePlaceholder = JSON.parse(state.brand);
if (imagePlaceholder) {
imagePlaceholder = imagePlaceholder.default.avatar;
} else {
imagePlaceholder = '';
}
var theme = getThemeColors(state.theme);
return {
theme: state.theme,
colors: theme,
profileStatus: state.profileStatus,
imagePlaceholder: imagePlaceholder,
};
};
const styles = StyleSheet.create({
drawerSection: {
marginTop: 15,
},
})
export default connect(mapStateToProps, {
onSelectTheme: setTheme,
})(SearchDrawer);
Header.js
import React from 'react';
import {
// StyleSheet,
// TouchableOpacity,
// TouchableWithoutFeedback,
Text,
View, TouchableOpacity,
AppState, TextInput, StyleSheet
} from 'react-native';
import { DrawerItem } from '#react-navigation/drawer';
import { DrawerActions } from '#react-navigation/native';
import { useNavigation } from '#react-navigation/native';
import { Appbar, Badge } from 'react-native-paper';
// import {colors} from '../../global/themes';
import { FontAwesomeIcon } from '#fortawesome/react-native-fontawesome'
import { faCoffee } from '#fortawesome/free-solid-svg-icons'
import Icon from 'react-native-vector-icons/FontAwesome';
import { connect } from 'react-redux';
import { setTheme } from './../../redux/actions/config';
// import {bindActionCreators} from 'redux';
import { getThemeColors } from '../../global/themes';
// import {useState} from 'react';
import { API_URL } from '../../global/config';
import AsyncStorage from '#react-native-community/async-storage';
import Menu, { MenuItem } from 'react-native-material-menu';
import messaging from '#react-native-firebase/messaging';
import notifee, { EventType } from '#notifee/react-native';
const ContentTitle = ({ title, color }) => (
<Appbar.Content
title={
// <View style={{zIndex: 99999999}}>
<Text style={{ fontSize: 20, color: color, zIndex: 99999999 }}>
{' '}
{title}{' '}
</Text>
// </View>
}
style={{ marginLeft: -10 }}
/>
);
function Header(props) {
const navigation = useNavigation();
console.log('Pera Navigation', navigation)
const { colors } = props;
const [notificationCount, setNotificationCount] = React.useState(0);
const [showMenu, setShowMenu] = React.useState(false);
const [appState, setAppState] = React.useState(AppState.currentState);
const [actions, setActions] = React.useState([]);
const menu = React.useRef();
const getUserId = async () => {
let userInfo = await AsyncStorage.getItem('userInfo');
if (userInfo) {
userInfo = JSON.parse(userInfo);
return userInfo.id;
}
};
function getNotificationCount() {
getUserId().then((id) => {
console.log(`${API_URL}newNotification`);
fetch(`${API_URL}newNotification`, {
method: 'post',
body: JSON.stringify({ user_id: id }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.then((res) => {
if (res.status) {
setNotificationCount(res.notification_count);
}
})
.catch((e) => {
console.log(e);
});
});
}
const _handleAppStateChange = (nextAppState) => {
getNotificationCount();
setAppState(nextAppState);
};
React.useEffect(() => {
AppState.addEventListener('change', (e) => _handleAppStateChange(e));
getNotificationCount();
notifee.createChannel({
id: 'custom-sound',
name: 'System Sound',
sound: 'notification__.mp3',
});
notifee.onForegroundEvent(({ type, detail }) => {
switch (type) {
case EventType.DISMISSED:
console.log('User dismissed notification', detail.notification);
break;
case EventType.PRESS:
let count = notificationCount;
setNotificationCount(0);
// alert('0 done');
navigation.navigate('Notifications', {
count: count,
screen: ' ',
});
break;
}
});
messaging().onMessage(async (remoteMessage) => {
// console.log('ok oko k');
await notifee.displayNotification({
title: remoteMessage.notification.title,
body: remoteMessage.notification.body,
android: {
channelId: 'custom-sound',
},
});
getNotificationCount();
});
messaging().onNotificationOpenedApp((remoteMessage) => {
let count = notificationCount;
setNotificationCount(0);
navigation.navigate('Notifications', {
count: count,
screen: 'header',
});
});
}, []);
React.useEffect(() => {
if (showMenu) {
menu.current.show();
}
}, [showMenu]);
return (
<Appbar.Header style={{ backgroundColor: colors.headerColor }}>
{props.back ? (
<Appbar.BackAction
onPress={() => (props.goBack ? props.goBack() : navigation.goBack())}
/>
) : (
<Appbar.Action
icon="menu"
onPress={() => navigation.openDrawer()}
size={28}
color={colors.textColor}
style={{ paddingLeft: 3 }}
/>
)}
<TouchableOpacity style={styles.searchSection}>
<TextInput
style={styles.input}
placeholder="Search By Order Id"
onPress={() =>navigation.openDrawer()}
/>
<Icon style={styles.searchIcon} name="search" size={20} color="#000" />
</TouchableOpacity>
{/* <TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon style={{marginLeft: 10}} name='menu'/>
</TouchableOpacity> */}
{/* <Button title='open the drawer' onPress={this.props.navigation.dispatch(DrawerActions.openDrawer('drawerOpenLeft'))}></Button>
<Button title='open the drawer right' onPress={this.props.navigation.dispatch(DrawerActions.openDrawer('drawerOpenRight'))}></Button> */}
<ContentTitle title={props.title} color={colors.textColor} />
{!props.hideNotification && (
<View>
{notificationCount > 0 && (
<Badge
visible={true}
size={16}
style={{ position: 'absolute', top: 10, right: 7, zIndex: 9999 }}>
<Text style={{ fontSize: 12 }}>{notificationCount}</Text>
</Badge>
)}
<Appbar.Action
icon="bell"
onPress={() => {
let count = notificationCount;
setNotificationCount(0);
navigation.navigate('Notifications', {
count: count,
screen: 'header',
});
}}
size={28}
color={colors.textColor}
style={{ paddingLeft: 3 }}
/>
</View>
)}
{props.selected && (
<>
<Appbar.Action
icon="delete"
onPress={() => props.onDeletePress()}
size={28}
color={colors.textColor}
style={{ left: 10 }}
/>
<Menu
ref={menu}
style={{ backgroundColor: colors.secondaryColor }}
onHidden={() => {
setShowMenu(false);
}}
button={
<Appbar.Action
icon="dots-vertical"
onPress={() => {
props.onActionPress().then((res) => {
console.log(res);
// if (res.status === 'TRUE') {
setShowMenu(res.status);
setActions(res.actions);
// } else {
// setShowMenu(false);
// }
});
}}
size={28}
color={colors.textColor}
/>
}>
{actions.length > 0 &&
actions.map((item, i) => {
return (
<>
{item.statusList !== undefined && (
<>
<MenuItem style={{ height: 40 }} disabled>
<Text style={{ color: colors.textLight }}>
{item.text}
</Text>
</MenuItem>
{item.statusList.map((list) => {
return (
<MenuItem
onPress={() => {
props.onMenuPress(item.type, list.VALUE);
menu.current.hide();
}}
key={i}>
<Text style={{ color: colors.textColor }}>
{' '}
{list.LABEL}
</Text>
</MenuItem>
);
})}
</>
)}
</>
);
})}
</Menu>
</>
)}
</Appbar.Header>
);
}
const mapStateToProps = (state) => {
var theme = getThemeColors(state.theme);
return { colors: theme };
};
export default connect(mapStateToProps, {
onSelectTheme: setTheme,
})(Header);
const styles = StyleSheet.create({
searchSection: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
searchIcon: {
padding: 10,
},
input: {
flex: 1,
height: 40,
padding: 10,
paddingLeft: 0,
backgroundColor: '#fff',
color: '#424242',
},
})
I don't know if I understood well but I need a little help.

How do i pass the value in counter component to the add to the To Cart Button

Hi I have a add minus component button that I want to pass the value to selected value to the "To Cart" button. If the value is zero the orders will not be added but if the value is > 0 then it will be added to cart.
below is my Add Minus button component
AddMinusButton.js
import React, { useState } from "react";
import { View, TouchableOpacity, Text, StyleSheet } from "react-native";
import Colors from "../../constants/Colors";
const AddMinusButton = () => {
const [value, setValue] = useState(0);
const minusValue = () => {
if (value > 0) {
setValue(value - 1);
} else {
setValue(0);
}
};
const plusValue = () => {
setValue(value + 1);
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.buttonLeft} onPress={minusValue}>
<Text>-</Text>
</TouchableOpacity>
<Text style={styles.quantity}> {value}</Text>
<TouchableOpacity style={styles.buttonRight} onPress={plusValue}>
<Text>+</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderRadius: 10,
height: 35,
overflow: "hidden",
borderColor: "black",
},
quantity: {
paddingHorizontal: 10,
marginRight: 3,
},
buttonLeft: {
alignItems: "center",
backgroundColor: Colors.primary,
borderRightWidth: 1,
borderRightColor: "black",
padding: 10,
},
buttonRight: {
alignItems: "center",
backgroundColor: Colors.primary,
borderLeftWidth: 1,
borderLeftColor: "black",
padding: 10,
},
});
export default AddMinusButton;
This is my Product Screen. I want to pass the value that is selected by the AddMinusButton component to the "To Cart". I am only able to made the button click and it will add one by one.
ProductOverviewScreen.js
import React, { useState, useEffect, useCallback } from "react";
import {
View,
Text,
FlatList,
Button,
Platform,
ActivityIndicator,
StyleSheet,
} from "react-native";
import { useSelector, useDispatch } from "react-redux";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import HeaderButton from "../../components/UI/HeaderButton";
import ProductItem from "../../components/shop/ProductItem";
import * as cartActions from "../../store/actions/cart";
import * as productsActions from "../../store/actions/products";
import Colors from "../../constants/Colors";
import * as AddMinusButton from "../../components/UI/AddMinusButton";
const ProductsOverviewScreen = (props) => {
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const products = useSelector((state) => state.products.availableProducts);
const dispatch = useDispatch();
const loadProducts = useCallback(async () => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(productsActions.fetchProducts());
} catch (err) {
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
const willFocusSub = props.navigation.addListener(
"willFocus",
loadProducts
);
return () => {
willFocusSub.remove();
};
}, [loadProducts]);
useEffect(() => {
setIsLoading(true);
loadProducts().then(() => {
setIsLoading(false);
});
}, [dispatch, loadProducts]);
const selectItemHandler = (id, title) => {
props.navigation.navigate("ProductDetail", {
productId: id,
productTitle: title,
});
};
if (error) {
return (
<View style={styles.centered}>
<Text>An error occurred!</Text>
<Button
title="Try again"
onPress={loadProducts}
color={Colors.primary}
/>
</View>
);
}
if (isLoading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colors.primary} />
</View>
);
}
if (!isLoading && products.length === 0) {
return (
<View style={styles.centered}>
<Text>No products found. Maybe start adding some!</Text>
</View>
);
}
return (
<FlatList
onRefresh={loadProducts}
refreshing={isRefreshing}
data={products}
keyExtractor={(item) => item.id}
renderItem={(itemData) => (
<ProductItem
image={itemData.item.imageUrl}
title={itemData.item.title}
price={itemData.item.price}
onSelect={() => {
selectItemHandler(itemData.item.id, itemData.item.title);
}}
>
<AddMinusButton />
<Button
color={Colors.primary}
title="To Cart"
onPress={() => {
dispatch(cartActions.addToCart(itemData.item));
}}
/>
</ProductItem>
)}
/>
);
};
ProductsOverviewScreen.navigationOptions = (navData) => {
return {
headerTitle: "All Products",
headerLeft: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title="Menu"
iconName={Platform.OS === "android" ? "md-menu" : "ios-menu"}
onPress={() => {
navData.navigation.toggleDrawer();
}}
/>
</HeaderButtons>
),
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title="Cart"
iconName={Platform.OS === "android" ? "md-cart" : "ios-cart"}
onPress={() => {
navData.navigation.navigate("Cart");
}}
/>
</HeaderButtons>
),
};
};
const styles = StyleSheet.create({
centered: { flex: 1, justifyContent: "center", alignItems: "center" },
});
export default ProductsOverviewScreen;