Error: Camera is not running. React native expo-camera - reactjs

Having camera permissions but still camera is not running its giving a blank (black) screen.
Sometimes it runs when refreshing but after that we again trigger the camera it will give the same blank again
Versions I am using.
"expo": "^41.0.0"
"expo-barcode-scanner": "~10.1.2"
"expo-camera": "~11.0.2"
"#react-navigation/native": "^5.9.4"
Here is my code before going to next screen. I am checking for camera permissions by pressing the FAB Button.
export default function ScannerScreen(props) {
const { navigation, route } = props;
const { colors, fonts } = useTheme();
const dispatch = useDispatch();
const { translations } = useContext(LocalizationContext);
const { scanner: strings } = translations;
const [data, setData] = useState('Not yet scanned')
const openQrScanner = async () => {
const { status } = await Camera.requestPermissionsAsync();
if (status === "granted") {
navigation.navigate('QRCodeScanner');
} else {
dispatch(notify(translations.allowCamera));
}
};
const goBack = () => {
navigation.goBack()
}
useEffect(() => {
if (route.params && route.params.data) {
setData(route.params.data)
}
}, [route])
return (
<View style={styles.container}>
<GeneralStatusBar backgroundColor="#fff" barStyle="dark-content" />
<View style={styles.header}>
<TouchableRipple style={styles.backButton} onPress={goBack} borderless={true} rippleColor={colors.rippleColor}>
<Ionicons name="md-arrow-back" size={24} color={colors.regularText} />
</TouchableRipple>
<View style={{ flex: 1 }}>
<Text style={{ ...fonts.medium, fontSize: 14, color: colors.regularText }}>{strings.pay}</Text>
</View>
</View>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ ...fonts.medium, fontSize: 16 }}>{data}</Text>
</View>
<FAB
icon={() => <MaterialIcons name="qr-code-scanner" size={24} color="white" />}
style={styles.fab}
onPress={openQrScanner}
/>
</View>
);
}
If user give the camera permissions then the will take to QRCodeScanner Screen.
export default function QRCodeScannerScreen(props) {
const { navigation, route } = props;
const { colors, fonts } = useTheme();
const { translations } = useContext(LocalizationContext);
const { scanner: strings } = translations;
const cameraRef = useRef()
const [scanned, setScanned] = useState(false);
const [flashMode, setFlashMode] = useState(false);
const [ratio, setRatio] = useState('4:3');
const { height, width } = Dimensions.get('window');
const screenRatio = height / width;
const [isRatioSet, setIsRatioSet] = useState(false);
const goBack = () => {
navigation.goBack()
}
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
navigation.navigate('Pay', { data: data });
// console.log('Type: ' + type + '\nData: ' + data)
};
const prepareRatio = async () => {
let desiredRatio = '4:3';
if (Platform.OS === 'android') {
if (cameraRef) {
try {
const ratios = await cameraRef.current.getSupportedRatiosAsync();
let distances = {};
let realRatios = {};
let minDistance = null;
for (const ratio of ratios) {
const parts = ratio.split(':');
const realRatio = parseInt(parts[0]) / parseInt(parts[1]);
realRatios[ratio] = realRatio;
const distance = screenRatio - realRatio;
distances[ratio] = realRatio;
if (minDistance == null) {
minDistance = ratio;
} else {
if (distance >= 0 && distance < distances[minDistance]) {
minDistance = ratio;
}
}
}
desiredRatio = minDistance;
setRatio(desiredRatio);
setIsRatioSet(true);
} catch (error) {
console.log('errorMessage', error);
}
}
}
};
const setCameraReady = async () => {
if (!isRatioSet) {
await prepareRatio();
}
};
return (
<View style={styles.container}>
<GeneralStatusBar backgroundColor="#0008" barStyle="light-content" />
<Camera
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
barCodeScannerSettings={{
barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
}}
flashMode={flashMode ? Camera.Constants.FlashMode.torch : Camera.Constants.FlashMode.off}
type={Camera.Constants.Type.back}
style={styles.barcodeScanner}
onCameraReady={setCameraReady}
ratio={ratio}
ref={cameraRef}
/>
<View style={styles.cameraOverlay}>
<View style={styles.overlayHeader}>
<TouchableRipple style={styles.backButton} onPress={goBack} borderless={true} rippleColor={colors.rippleColor}>
<Ionicons name="md-arrow-back" size={24} color={'#fff'} />
</TouchableRipple>
<View style={{ flex: 1 }}>
<Text style={{ ...fonts.medium, fontSize: 14, color: '#fff' }}>{strings.scanAnyQr}</Text>
</View>
<TouchableRipple style={styles.backButton} onPress={() => setFlashMode(!flashMode)} borderless={true} rippleColor={colors.rippleColor}>
{flashMode ?
<MaterialCommunityIcons name="flashlight" size={20} color="white" />
:
<MaterialCommunityIcons name="flashlight-off" size={20} color="white" />
}
</TouchableRipple>
</View>
<View style={styles.scannerContainer}>
<View style={styles.scannerBox}>
{/* <View style={{ ...styles.barcodebox, borderColor: colors.button, borderWidth: 2 }}>
<Text style={{ fontSize: 12, color: "#fff" }}>Please align the QR within the scanner.</Text>
</View> */}
</View>
</View>
</View>
</View>
);
}

Related

Load component after api is fetched

I am fetching json from API and then I want to display the component
const [jsonCat, setjsonCat] = useState([]);
useEffect(() => {
refreshCat();
}, []);
const refreshCat = async () => {
try {
console.log("refreshing categories");
setjsonCat([]);
getCat().then((response) => {
setjsonCat(response);
});
} catch (e) {
console.log(e);
}
};
const CarousalLoop = (props) => {
const BANNER_Hs = 1;
if (jsonCat.length == 0) {
return <View></View>;
}
const listItemstest = jsonCat.map((link) => {
console.log(link.name);
<View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>;
});
return (
<View style={{ height: 220, backgroundColor: "brown" }}>
{listItemstest}
</View>
);
};
And Finally my render component has
{jsonCat.length ? <CarousalLoop /> : <ActivityIndicator />}
When I run this code , Activity indicator is shown until API request is fetched and then json is also received properly , console.log(link.name)
is printing the names correctly but CarousalLoop is displayed without any listitems (just brown view component is shown) .
Either you use return keyword
const listItemstest = jsonCat.map((link) => {
console.log(link.name);
return(
<View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>
)
});
Or wrap in parenthesis
const listItemstest = jsonCat.map((link) => (
<View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>;
));
You need to return the list of items to render from listItemstest, like this.
const listItemstest = jsonCat.map((link) => {
console.log(link.name);
return <View>
<Text style={{ color: "red" }}>{link.name}</Text>
</View>;
});

How to display a value back in parent component in react native?

I have a page which shows list of coins.
From there I navigated to another component and passing the coin name as param.
From the second page, I have filtered all the orders based on the coin name received in params.Here, I calculated the average value. How to pass this back to parent page so that I can see the average value of orders beside each coin?
//screen1 code:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
export default function CoinCreate(props) {
const [coinText, setCoinText] = useState("");
const [coins, setCoins] = useState([]);
const coinRef = firebase.firestore().collection("coins");
const userID = props.extraData.id;
useEffect(() => {
coinRef
.where("authorID", "==", userID)
.orderBy("createdAt", "desc")
.onSnapshot(
(querySnapshot) => {
const newCoins = [];
querySnapshot.forEach((doc) => {
const coin = doc.data();
coin.id = doc.id;
newCoins.push(coin);
});
setCoins(newCoins);
},
(error) => {
console.log(error);
}
);
}, []);
const onAddButtonPress = () => {
if (coinText && coinText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: coinText,
authorID: userID,
createdAt: timestamp,
};
coinRef
.add(data)
.then((_doc) => {
setCoinText("");
Keyboard.dismiss();
})
.catch((error) => {
alert(error);
});
}
};
const renderCoin = ({ item, index }) => {
return (
<View style={styles1.rowFront}>
<TouchableWithoutFeedback
onPress={() =>
props.navigation.navigate("Orders", {
coin: item.text,
userID: userID,
})
}
>
<Text>
{index}. {item.text}
</Text>
</TouchableWithoutFeedback>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new coin"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setCoinText(text)}
value={coinText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{coins && (
<SwipeListView
data={coins}
keyExtractor={(item) => item.id}
renderItem={renderCoin}
removeClippedSubviews={true}
/>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
//screen2 code:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
} from "react-native";
import { Avatar, Button, Card, Title, Paragraph } from "react-native-paper";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { Icon } from "react-native-elements";
import { createIconSetFromFontello } from "#expo/vector-icons";
export default function OrderList(props) {
const LeftContent = (props) => <Avatar.Icon {...props} icon="folder" />;
const [orderText, setOrderText] = useState("");
const [orders, setOrders] = useState([]);
const orderRef = firebase.firestore().collection("orders");
const userID = props.route.params.userID;
const coin = props.route.params.coin;
//averageValue = (totalCost / totalCount).toString();
const [averageValue, setAverageValue] = useState("");
useEffect(() => {
orderRef
.where("authorID", "==", userID)
.where("name", "==", coin)
.orderBy("createdAt")
.onSnapshot(
(querySnapshot) => {
const newOrders = [];
querySnapshot.forEach((doc) => {
const order = doc.data();
order.id = doc.id;
newOrders.push(order);
});
setOrders(newOrders);
},
(error) => {
console.log(error);
}
);
}, []);
useEffect(() => {
//calculate and set anything like totalCost, averageValue, etc here
console.log("---came to orders effect---");
//console.log(orders);
let totalCost = 0;
let totalCount = 0;
orders.forEach((item, index) => {
console.log(item);
console.log(index);
totalCost += parseFloat(item.amount);
totalCount += parseFloat(item.count);
});
setAverageValue((totalCost / totalCount).toString());
}, [orders]);
/*
useEffect(() => {
let avg = (parseFloat(totalCost) / parseFloat(totalCount)).toString();
console.log("Avg:" + avg);
setAverageValue(avg);
}, [totalCount, totalCost]);
*/
const onAddButtonPress = () => {
props.navigation.navigate("CreateOrder", {
coin: coin,
userID: userID,
orderRef,
});
};
const renderOrder = ({ item, index }) => {
//console.log("----------------------");
//console.log(item.createdAt.toDate().toString());
//console.log("----------------------");
//setTotalCost(parseFloat(totalCost) + parseFloat(item.price));
//setTotalCount(parseFloat(totalCount) + parseFloat(item.count));
//totalCost = parseFloat(totalCost) + parseFloat(item.price);
//totalCount = parseFloat(totalCount) + parseFloat(item.count);
//console.log(totalCost);
//console.log(totalCount);
return (
<View style={styles1.rowFront}>
<Text>
{index}. {item.price} {item.amount} {item.count}
{"\n" + item.createdAt.toDate().toString()}
</Text>
<Icon name={"flight-takeoff"} />
</View>
);
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Click here to create new order..</Text>
</TouchableOpacity>
{orders.length === 0 && (
<Text>Please order some coins in currency: {coin}</Text>
)}
{orders && orders.length > 0 && (
<>
<Text>Average Value: {averageValue}</Text>
<SwipeListView
data={orders}
keyExtractor={(item) => item.id}
renderItem={renderOrder}
removeClippedSubviews={true}
/>
</>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
To simplify the process, I created a normal function(not react component) with orderListData(coin,userID) in a separate file and imported that and calling that function from the screen 1, but getting error as
TypeError: (0, _OrderListData.orderListData) is not a function. (In '(0, _OrderListData.orderListData)("ada", "nMOpBupcptZF8YlxiJYFX7vPMtC2")', '(0, _OrderListData.orderListData)' is undefined)
My code:
OrderListData.js
import { firebase } from "../../firebase/config";
export default function orderListData(coin, userID) {
let orders = [];
const orderRef = firebase.firestore().collection("orders");
//const userID = props.route.params.userID;
//const coin = props.route.params.coin;
let totalCost = 0;
let totalCount = 0;
let averagePrice = 0;
orderRef
.where("authorID", "==", userID)
.where("name", "==", coin)
.orderBy("createdAt")
.onSnapshot(
(querySnapshot) => {
querySnapshot.forEach((doc) => {
const order = doc.data();
order.id = doc.id;
orders.push(order);
});
},
(error) => {
console.log(error);
}
);
orders.forEach((item, index) => {
totalCost += parseFloat(item.amount);
totalCount += parseFloat(item.count);
});
averagePrice = totalCost / totalCount;
return {
averagePrice,
totalCost,
totalCount,
};
}
And the modified screen 1:
import React, { useEffect, useState } from "react";
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import { SwipeListView } from "react-native-swipe-list-view";
import styles from "./styles";
import { firebase } from "../../firebase/config";
import { orderListData } from "./OrderListData";
export default function CoinCreate(props) {
console.log("testing");
console.log(orderListData("ada", "nMOpBupcptZF8YlxiJYFX7vPMtC2"));
const [coinText, setCoinText] = useState("");
const [coins, setCoins] = useState([]);
const coinRef = firebase.firestore().collection("coins");
const userID = props.extraData.id;
useEffect(() => {
coinRef
.where("authorID", "==", userID)
.orderBy("createdAt", "desc")
.onSnapshot(
(querySnapshot) => {
const newCoins = [];
querySnapshot.forEach((doc) => {
const coin = doc.data();
coin.id = doc.id;
newCoins.push(coin);
});
setCoins(newCoins);
},
(error) => {
console.log(error);
}
);
}, []);
const onAddButtonPress = () => {
if (coinText && coinText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: coinText,
authorID: userID,
createdAt: timestamp,
};
coinRef
.add(data)
.then((_doc) => {
setCoinText("");
Keyboard.dismiss();
})
.catch((error) => {
alert(error);
});
}
};
const renderCoin = ({ item, index }) => {
let { averagePrice, totalCost, totalCount } = orderListData(
item.text,
userID
);
return (
<View style={styles1.rowFront}>
<TouchableWithoutFeedback
onPress={() =>
props.navigation.navigate("Orders", {
coin: item.text,
userID: userID,
})
}
>
<Text>
{index}. {item.text} {averagePrice}
</Text>
</TouchableWithoutFeedback>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new coin"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setCoinText(text)}
value={coinText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{coins && (
<SwipeListView
data={coins}
keyExtractor={(item) => item.id}
renderItem={renderCoin}
removeClippedSubviews={true}
/>
)}
</View>
);
}
const styles1 = StyleSheet.create({
rowFront: {
alignItems: "center",
backgroundColor: "#FFF",
borderBottomWidth: 0.25,
justifyContent: "center",
height: 50,
},
rowBack: {
alignItems: "center",
backgroundColor: "#DDD",
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
paddingLeft: 15,
},
backRightBtn: {
alignItems: "center",
bottom: 0,
justifyContent: "center",
position: "absolute",
top: 0,
width: 75,
backgroundColor: "red",
right: 0,
},
});
Any suggestion on this please.
There are different ways to solve this problem,
Your requirement is to update one screen based on another screen, so you can think of using something state management for this but in your case you have the trigger of going back so use the functions of the navigation library to do that.
So I'll put a working example here using a simple scenario of sending an array
Let say that you have a home screen, from here we go to a CreateOrder screen and it will send data do this screen via navigation.navigate (navigate function will navigate to an existing screen or push a new screen so here you will be sent back).
function HomeScreen({ navigation, route }) {
React.useEffect(() => {
if (route.params?.orders) {
}
}, [route.params?.orders]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Create post"
onPress={() => navigation.navigate('CreateOrder')}
/>
<Text style={{ margin: 10 }}>Post: {route.params?.orders?.length}</Text>
</View>
);
}
You can use the useEffect to update if the param is updated and use the route prop.
The second screen would override the back button and also have a button to navigate back. Here we send the orders array via navigation.navigate
function CreateOrdersScreen({ navigation, route }) {
const [orders, setOrders] = React.useState('');
React.useLayoutEffect(() => {
navigation.setOptions({
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
navigation.navigate('Home', { orders });
}}
/>
),
});
}, [navigation, orders]);
return (
<>
<Text>{orders.length}</Text>
<Button
title="Add"
onPress={() => {
setOrders([...orders, 1]);
}}
/>
<Button
title="Done"
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { orders },
merge: true,
});
}}
/>
</>
);
}
You can see a running example here
https://snack.expo.io/#guruparan/5b84d0
References
https://reactnavigation.org/docs/params/#passing-params-to-a-previous-screen
https://reactnavigation.org/docs/header-buttons/#overriding-the-back-button

react native conditionally fetch data too much re-rendering issue

I want to achieve such scenario in my application:
when the user taps on a Company for checking its details I want to show all products of the company first.
when the user taps on a Category in the Details page I want to show only products related to that category.
for both cases I need to call API with specific arguments so here's my code:
Card Details Page:
const CardDetails = (props) => {
const dispatch = useDispatch();
const companyId = props.route.params.id;
const companyName = props.route.params.companyName;
const companyImage = props.route.params.companyImage;
const companyLocation = props.route.params.companyLocation;
const [isCatLoading, setIsCatLoading] = useState(true);
const [isProductLoading, setIsProductLoading] = useState(true);
const [CompanyCategories, setCompanyCategories] = useState([]);
let products = [];
console.log(products)
const fetchCategories = async () => {
const response = await fetch(`myURL`);
const data = await response.json();
setCompanyCategories(data)
setIsCatLoading(false)
};
const fetchProducts = () => {
dispatch(SelectedProductsByCat.fetchSelectedCatProducts(companyId, [7, 8, 10, 11]))
.then(
setIsProductLoading(false)
)
}
if (setIsProductLoading) {
products = useSelector(state => state.products.availableSelectedCatProducts)
}
useEffect(() => {
fetchCategories()
}, [products])
useEffect(() => {
fetchProducts()
}, [products])
return (
<ScrollView showsVerticalScrollIndicator={false} style={styles.screen}>
<View style={styles.titleContainer}>
<Text style={styles.titleText}>{companyName}</Text>
</View>
<View style={styles.imageContainer}>
<View style={styles.mainImageContainer}>
<Image style={styles.mainImage} source={{ uri: companyImage }} />
</View>
</View>
<View style={styles.optionsContainer}>
<Option title={companyLocation} />
</View>
<View style={styles.typeSelectionContainer}>
{
isCatLoading ?
<DotIndicator color='#253D59' size={20} count={5} />
:
<ScrollView contentContainerStyle={styles.horizontalScrollContainer} horizontal={true} showsHorizontalScrollIndicator={false}>
<CategorySelection
companyId={companyId}
options={CompanyCategories.map((el, i) => {
return { id: el.id, name: el.title, icon: el.icon, selected: false }
})}
/>
</ScrollView>
}
</View>
{
isProductLoading ?
<View>
<DotIndicator color='#253D59' size={20} count={5} />
</View>
:
<View style={styles.resultsContainer}>
{
products != 'undefined' && products.length > 0 ?
products.map((el, index) => {
return (
<TouchableOpacity
key={el.id.toString()}
style={styles.resultImageContainer}
onPress={() => props.navigation.navigate('OfficeDetails', {
productId: el.id.toString(),
productName: el.productName,
productPrice: el.productPrice,
productImages: el.groupOfImage
})}
>
<Image style={styles.mainImage} source={{ uri: el.groupOfImage[0].imageAddress }} />
<View style={styles.seatsContainer}>
<Text style={styles.seatsText}>{el.productPrice} <Text style={styles.price}>₺</Text></Text>
</View>
</TouchableOpacity>
)
})
:
<View style={{ marginTop: 40 }}>
<NoData size={wp(40)} />
</View>
}
</View>
}
</ScrollView>
)
}
and this is the Categories Component:
const CategorySelection = (props) => {
const dispatch = useDispatch();
const companyId = props.companyId
const [options, setOptions] = useState(props.options.map((el, index) => {
return {
id: el.id,
name: el.name,
selected: el.selected
}
}))
const selectedCategoriesId = options.filter(el => el.selected).map(el => el.id);
useEffect(() => {
fetchProductsHandler();
}, [options])
const fetchProductsHandler = () => {
if(selectedCategoriesId.length == 0) {
dispatch(SelectedProductsByCat.fetchSelectedCatProducts(companyId, options.map((el, index) => el.id)));
}
}
const onRadioBtnClick = (item) => {
let updatedState = options.map((el) =>{
if(el.id == item.id) {
return { ...el, selected: !el.selected }
} else {
return { ...el, selected: el.selected }
}
}
);
setOptions(updatedState)
};
return (
<View style={styles.mainContainer}>
{
options.map((item, index) => {
return (
<TouchableOpacity
onPress={() => {
onRadioBtnClick(item)
}
}
key={item.id}
selected={item.selected}
style={[styles.buttonContainer, props.buttonStyle]}>
<View style={[styles.button,
item.selected == true ? styles.selectedButton : null
]}>
<Text style={[styles.buttonText,
item.selected == true ? styles.selectedButtonText : null,
{fontSize : item.name.split(' ')[0].length > 10 ? hp(1.5) : hp(2.1)}
]}>{item.name}</Text>
</View>
</TouchableOpacity>
)
})
}
</View>
)
}
and here's the screenshot of the page:
again what I need to achieve here is to show all products at first, but when the user tap on categories I need to show related products, and also when the user deselects categories all products show again.
if you need more code for the project just let me know in the comments. any help would be appreciated <3

React Native onPress doesn't work on IOS, but on Web it does

community!
I am building my first React-Native app and I have problem running this part of the code on IOS.
There is a function passed to onPress prop of a Button, that works on web but when I tried it on IOS nothing happends when pressing the button. The function should update the state of the parent component so it render's the next part of the Game form.
(There is a complete screen code sample at the bottom and you can also check the entire app in this git repository: https://github.com/dogaruemiliano/claim-app)
const handlePress = () => {
handleSubmit(maxPoints)
}
return (
<>
<Text style={styles.header}>Max points</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={maxPoints}
onChangeText={value => setMaxPoints(value)}
keyboardType="numeric"
/>
<KeyboardAvoidingView
behavior='padding'
>
<View>
<TouchableOpacity style={styles.btn}>
<Button
title="Next"
onPress={() => handlePress()} // here is the problem
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</>
)
The function called in the handlePress,
handleSubmit(maxPoints)
is received in the props of the component from the parent component that handles the logic up to the point where it send a POST request to the API.
Here is the complete code of the screen for New Game Creation.
import React, { useRef, useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { SafeAreaView, View, Text, StyleSheet, Button, Platform, KeyboardAvoidingView } from 'react-native'
import { TextInput, TouchableOpacity } from 'react-native-gesture-handler'
import Navbar from '../components/Navbar'
import COLORS from '../constants/Colors'
import { createGame } from '../actions'
const MaxPointsInput = (props) => {
const { handleSubmit } = props
const [maxPoints, setMaxPoints] = useState("")
const [done, setDone] = useState(false)
const inputRef = useRef(null)
const handlePress = () => {
setDone(true) // to test if function is executed
handleSubmit(maxPoints)
}
useEffect(() => {
inputRef.current.focus()
}, [])
return (
<>
<Text style={styles.header}>Max points</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={maxPoints}
onChangeText={value => setMaxPoints(value)}
keyboardType="numeric"
/>
<KeyboardAvoidingView
behavior='padding'
>
<View>
<TouchableOpacity style={styles.btn}>
<Button
title={done ? "Done..." : "Next"}
onPress={() => handlePress()}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</>
)
}
const PlayersInput = (props) => {
const { handleSubmit, currentUser } = props
const [players, setPlayers] = useState([])
const [nameInputValue, setNameInputValue] = useState("")
const inputRef = useRef()
useEffect(() => {
inputRef.current.focus()
}, [])
const addPlayer = () => {
setNameInputValue("")
setPlayers([...players, nameInputValue])
inputRef.current.value = ""
inputRef.current.focus()
}
return (
<>
<Text style={styles.header}>Players</Text>
<View style={{ flex: 1 }}>
<Text>Player #1: {currentUser.attributes.name} (you)</Text>
{players.map((player, index) => {
return(
<Text key={player}>Player #{index + 2}: {player}</Text>
)
})}
<Text>Player #{players.length + 2}</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={nameInputValue}
onChangeText={value => setNameInputValue(value)}
keyboardType="numeric"
/>
</View>
<KeyboardAvoidingView
behavior='padding'
style={{ marginBottom: 200, flex: 1 }}
>
<TouchableOpacity style={styles.btn}>
<Button
title="Add"
onPress={addPlayer}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
<TouchableOpacity style={styles.btn}>
<Button
title="Done"
onPress={() => handleSubmit(players)}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</KeyboardAvoidingView>
</>
)
}
const Confirmation = (props) => {
const { handleSubmit, maxPoints, players, currentUser } = props
return (
<>
<Text style={styles.header}>Details</Text>
<View style={{ flex: 1 }}>
<Text>Max points: {maxPoints}</Text>
<Text>Players: </Text>
<Text>1. {currentUser.attributes.name}</Text>
{players.map((player, index) => {
return (
<Text key={player}>{index + 2}. {player}</Text>
)
})}
</View>
<KeyboardAvoidingView
behavior='padding'
>
<View style={styles.btn}>
<Button
title="Confirm"
onPress={() => handleSubmit({maxPoints, players}, currentUser)}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</View>
</KeyboardAvoidingView>
</>
)
}
function NewGameScreen(props) {
const { navigation, currentUser, createGame } = props
const [maxPoints, setMaxPoints] = useState("")
const [players, setPlayers] = useState([])
const [showMaxPointsInput, setShowMaxPointsInput] = useState(true)
const [showPlayersInput, setShowPlayersInput] = useState(false)
const [showConfirmation, setShowConfirmation] = useState(false)
const handleMaxPointsSubmit = (value) => {
setMaxPoints(value)
setShowMaxPointsInput(false)
setShowPlayersInput(true)
}
const handlePlayersSubmit = (value) => {
setPlayers(value)
setShowPlayersInput(false)
setShowConfirmation(true)
}
const handleConfirmaton = (data, currentUser) => {
createGame({data, currentUser, navigation})
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.inner}>
{ showMaxPointsInput &&
<MaxPointsInput handleSubmit={handleMaxPointsSubmit}/>
}
{ showPlayersInput &&
<PlayersInput handleSubmit={handlePlayersSubmit} currentUser={currentUser} />
}
{
showConfirmation &&
<Confirmation maxPoints={maxPoints} currentUser={currentUser} players={players} handleSubmit={handleConfirmaton} />
}
</View>
<Navbar navigation={navigation} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 'auto',
marginTop: 48
},
inner: {
padding: 24,
paddingBottom: 80,
justifyContent: "center",
width: (Platform.OS === 'web' ? 300 : null),
marginTop: 48,
},
btn: {
borderRadius: 6,
marginTop: 12,
marginBottom: 2,
backgroundColor: COLORS.black,
},
btnOutline: {
borderRadius: 6,
marginTop: 12,
backgroundColor: COLORS.transparent,
borderWidth: 2,
borderColor: COLORS.black,
color: COLORS.black,
},
header: {
fontSize: 32,
textAlign: 'center',
marginBottom: 32,
},
scrollView: {
paddingHorizontal: 20,
},
input: {
textAlign: 'center',
height: 50,
fontSize: 32,
marginBottom: 20,
borderBottomWidth: 2,
borderColor: '#dbdbdb',
paddingBottom: 5,
},
})
const mapStateToProps = (state) => {
return {
currentUser: state.currentUser
}
}
const mapDispatchToProps = (dispatch) => {
return {
createGame: (props) => dispatch(createGame(props))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewGameScreen)
Thank you for reading!
From your code block, it seems that you are wrapping a button inside a TouchableOpacity Component which is imported from react-native-gesture-handler.
You can refactor code into, like removing unwanted wrapping with Touchable Elements if not required.
<View style={styles.btn}>
<Button
title={done ? "Done..." : "Next"}
onPress={handlePress}
color={Platform.OS === 'ios' ? 'white' : 'black'} />
</View>
Import TouchableOpacity from react-native
import { Text, View, StyleSheet ,TextInput, TouchableOpacity} from 'react-native';
Happy Coding!

Flatlist with calling API on scroll does not work properly

I am using Function component in react native.
In that I am use Flatlist for data view. on onEndReached function of FlatList i am calling API for fetching data.
My Problem is
on scroll API call twice some times
on the Finish of all data. ActivityIndicator is showing in ListFooterComponent function.
Can you please help me.
Thanks in Advance.
Here is my code:
import React, { useState, useEffect } from "react";
import {
View,
StyleSheet,
ActivityIndicator,
Image,
FlatList,
TouchableHighlight,
} from "react-native";
import MainStyle from "../constants/Style";
import Layout from "../constants/Layout";
import Colors from "../constants/Colors";
import Services from "../Services";
const LIMIT_PER_PAGE = 10;
let fetching = false;
let isSubscribed = true;
let isListEnd = false;
let pageNo = 1;
var sortby = "latest";
let width = Layout.window.width / 2 - 16;
export default function PostListScreen(props) {
const [posts, setPosts] = useState([]);
fetchAllLatestPost = function () {
console.log("fetchAllLatestPost", fetching, isListEnd);
if (!fetching && !isListEnd) {
fetching = true;
Services.getAllPost(sortby, pageNo, LIMIT_PER_PAGE)
.then(function (res) {
if (!!res) {
console.log("res.length", res.length)
if (!!res.length && isSubscribed) {
pageNo++;
setPosts((posts) => {
posts = [...posts, ...res];
return posts;
});
} else {
isListEnd = true;
}
fetching = false;
}
});
}
};
useEffect(() => {
fetching = false;
isListEnd = false;
pageNo = 1;
isSubscribed = true;
fetchAllLatestPost();
return () => (isSubscribed = false);
}, []);
let readyImage = function (post) {
if (!!post.urls && !!post.urls.regular) {
return typeof post.urls.regular === "string"
? { uri: post.urls.regular }
: post.urls.regular;
} else {
return require("../assets/images/logo_white.png");
}
};
let renderItem = function (post, index) {
return (
<TouchableHighlight onPress={() => onPressPost(post)} key={index}>
<View style={styles.item}>
<Image
source={readyImage(post)}
style={{
flex: 1,
width: null,
height: null,
resizeMode: "cover",
borderRadius: 4,
}}
/>
</View>
</TouchableHighlight>
);
};
let renderFooter = function () {
return (
<View style={styles.footer}>
{console.log("fetching in footer ", fetching)}
{!!fetching ? (
<ActivityIndicator color="white" style={{ margin: 15 }} />
) : null}
</View>
);
};
return (
<View style={MainStyle.wrapper}>
<FlatList
numColumns={2}
keyExtractor={(item, index) => index.toString()}
data={posts}
onEndReached={() => fetchAllLatestPost()}
onEndReachedThreshold={0.5}
renderItem={({ item, index }) => <View>{renderItem(item, index)}</View>}
getItemLayout={(data, index) => ({
length: width,
offset: (width/2) * index,
index,
})}
ListFooterComponent={renderFooter()}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
flexWrap: "wrap",
},
item: {
height: width,
width: width,
margin: 8,
},
footer: {
padding: 10,
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
},
});
This is how I handled it
<FlatList
data={this.state.posts}
onRefresh={() => {
if (!this.props.posts.fetching) {
this.setState({
page: 1,
checkData: true,
posts: [],
});
this.props.postsRequest({page: 1});
}
}}
// onEndReached={}
keyExtractor={item => item.uid}
onMomentumScrollEnd={() => {
if (!this.props.posts.fetching) {
this.props.postsRequest({page: this.state.page + 1});
this.setState({page: this.state.page + 1, checkData: true});
}
}}
onEndReachedThreshold={0}
refreshing={this.props.posts.fetching}
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingBottom: 50}}
style={{padding: 10, marginVertical: 10}}
renderItem={item => (
<View style={{marginVertical: 5}}>
<Posts post={item.item} navigation={this.props.navigation} />
</View>
)}
/>

Resources