react native conditionally fetch data too much re-rendering issue - reactjs

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

Related

Mobx store do not update with observer

I have a simple react native app with two screens.
First screen is the list, where you see your selected groups, and you can remove them, by clicking on trash icon:
export const Settings: NavioScreen = observer(({ }) => {
...
return (
<View flex bg-bgColor padding-20>
<FlashList
contentInsetAdjustmentBehavior="always"
data={toJS(ui.savedGroups)}
renderItem={({item}) => <ListItem item={item} />}
estimatedItemSize={20}
/>
</View>
);
});
};
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<TouchableOpacity onPress={() => ui.deleteGroup(item)}>
<Icon name={'trash'}/>
</TouchableOpacity>
</View>
);
};
The second screen is also the list, where you can add and remove the subjects from the list:
export const Playground: NavioScreen = observer(() => {
...
const groupsToShow =
ui.search && ui.search.length > 0
? ui.groups.filter((p) =>
p.name.toLowerCase().includes(ui.search.toLowerCase())
)
: ui.groups;
return (
<View >
<FlashList
data={toJS(groupsToShow)}
renderItem={({item}) => <ListItem item={item} />}
/>
</View>
);
});
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<View>
<If
_={ui.isGroupSaved(item)}
_then={
<TouchableOpacity onPress={(e) => {ui.deleteGroup(item)}}>
<AntDesign name="heart" size={20} color={Colors.primary} />
</TouchableOpacity>
}
_else={
<TouchableOpacity onPress={(e) => {ui.addGroup(item)}}>
<AntDesign name="hearto" size={20} color={Colors.primary} />
</TouchableOpacity>
}
/>
</View>
</View>
);
};
And now when I remove the group from the first list, the heart icon do not update on the second list. But it should, because there is an if statement on second list, that checks if the group is saved. And if it is not, the heart should have the name="hearto"
I have tried to use the state instead mobx library but it does not also help.
Here is my store written with mobx:
export class UIStore implements IStore {
savedGroups = [];
constructor() {
makeAutoObservable(this);
makePersistable(this, {
name: UIStore.name,
properties: ['savedGroups'],
});
}
addGroup = (group: any) => {
if (true === this.isGroupSaved(group)) {
return;
}
this.savedGroups.push(group);
}
isGroupSaved = (group: any) => {
return -1 !== this.savedGroups.findIndex(g => g.id === group.id);
}
deleteGroup = (groupToDelete: any) => {
this.savedGroups = this.savedGroups.filter((group) => group.id !== groupToDelete.id);
}
}

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

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>
);
}

How do I get information from the database using another user's userId?

What I want to do is to access the data of the person who sent the post using user id. I can get the id but I can't use it to pull information from the database
I got the id of the person who shared the post
After getting the id of the person who shared the post, I want to use the id to pull the information from the database, but it acts as if there is no id.
const user = firebase.auth().currentUser.uid;
const db = firebase.database();
const ref = db.ref('kullaniciBilgiler/'+`${user}`);
const refComment = db.ref('yorumlar/'+`${user}`+'/commentData');
const refPost = db.ref('Post')
export default postDetails = ({navigation, route}) => {
const [currentDate, setCurrentDate] = useState('');
const [userId, setUserId] = useState('');
const [commentName, setcommentName] = useState([]);
const [commentText, setcommentText] = useState('');
const [commentData, setcommentData] = useState([]);
const [postDetailsData, setpostDetailsData] = useState([]);
const [postingInf, setPostingInf] = useState([]);
const { title } = route.params;
//console.log(title)
function getComments(){
const refComment = db.ref('yorumlar/'+`${user}`+'/commentData');
refComment.on("value",(snapshot)=>{
if(snapshot.val()){
const data=snapshot.val();
const comments=Object.values(data) || [];
//console.log(data);
setcommentData(comments)
}
})
}
function getPostingInf(){
const refPostingİnf = db.ref('kullaniciBilgiler/'+ userId);
refPostingİnf.on("value",(snapshot)=>{
if(snapshot.val()){
const data=snapshot.val();
const comments=Object.values(data) || [];
console.log(data);
setPostingInf(comments)
}else{
alert('zot')
}
})
} ///SORUNLU KISIM
function getPost(){
var list=[];
refPost.child(title).on("value",(snapshot)=>{
if(snapshot.val()){
list.push({
text:snapshot.val().text,
id:snapshot.val().id,
postTime:snapshot.val().postTime,
title:snapshot.val().title,
category:snapshot.val().category
}
);
setpostDetailsData(list);
//console.log(snapshot.val().text)
//console.log(child.val().title,'tamam',child.val())
//const data =snapshot.val();
//const postDetails = Object.values(data) || [];
//setpostDetailsData(postDetails);
//console.log(data)
}else{
//console.log('eşleşmedi')
}
});
}
ref.once('value').then(snapshot=>{
var li=[]
var list=[]
snapshot.forEach((child) => {
li.push({
name : child.val().name,
surname : child.val().surname,
})
list.push(
child.val().name + ' '+ child.val().surname
)
}
)
setfirebaseData(li)
setcommentName(list)
//console.log(list);
});
getComments();
getPost();
getPostingInf();
} , []);
return(
<SafeAreaView style={styles.container}>
<ScrollView>
<View style={styles.headerBarView}>
<TouchableOpacity style={{position: 'absolute', left:10}}>
<Icon name='arrow-left'
style={styles.Icons}
/>
</TouchableOpacity>
<TouchableOpacity style={{position: 'absolute', right:60}
}>
<Icon name='bookmark'
style={styles.Icons}
/>
</TouchableOpacity>
<TouchableOpacity style={
{position: 'absolute', right:10}}>
<Icon name='share-square'
style={styles.Icons}
/>
</TouchableOpacity>
</View>
<Card style={{marginBottom:5}}>
<FlatList
data={postDetailsData}
keyExtractor={(item)=>item.key}
renderItem={({item,index})=>{
setUserId(item.id)
return(
<View style={styles.questionBoxView}>
<View style={styles.questionTitleView}>
<Text style={styles.questionTitleText}>
{item.title}
</Text>
</View>
<View style={styles.questionerProfileView}>
<Image source={{uri:'https://picsum.photos/700'}}
style={styles.AvatarImage}
/>
<Text style={styles.UserNameText}>{item.name+' '+item.surname}</Text>
<TouchableOpacity style={styles.followPosterTouch}>
<Text style={styles.followPosterTouchText}>Takip Et</Text>
</TouchableOpacity>
</View>
<View style={styles.questionDetailsView}>
<Text style={{fontSize:15,padding:10}}>{item.text}</Text>
</View>
<View style={styles.categoryBoxes}>
{item.category.map((a,b)=>{
return <TouchableOpacity
style={styles.categoryBoxesTouch}
>
<Text style={styles.categoryBoxesText}>{a.name}</Text>
</TouchableOpacity>
})}
</View>
</View>
)
}
}
/>

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>;
});

Show View when scroll up Scrollview

How to limit the quantity of View inside of a scrollview.
My component take too much time to render, because the map function renders too many views. I need to show only 10 views, and when scroll up, renders more 10.
I'm using react native, hooks and typescript.
First of all, if you have a large number of list data don't use scrollview. Because initially, it loads all the data to scrollview component & it costs performance as well.
Use flatlist in react-native instead of scrollview & you can limit the number of items to render in the initially using initialNumToRender. When you reach the end of the scroll position you can call onEndReached method to load more data.
A sample will like this
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
class FlatListDemo extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
};
handleRefresh = () => {
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
this.makeRemoteRequest();
}
);
};
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
this.makeRemoteRequest();
}
);
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return <SearchBar placeholder="Type Here..." lightTheme round />;
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
keyExtractor={item => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
</List>
);
}
}
export default FlatListDemo;
Check this for more informations.
I changed to Flatlist! But initialNumToRender is not working as expected.
The flatlist is rendering all 150 transactions, not only 15, and i have no idea what to do.
I'm running .map() from another array with all others transactions to create newMonths with only those transactions that i want to use data={newMonths}.
let newMonths = [];
const createArrayMonth = histInfos.map(function (info) {
if (info.created_at.slice(5, 7) === month) {
newMonths = [info].concat(newMonths);
}
});
them, i created my component:
function Item({ value }: { value: any }) {
let createdDay = value.item.created_at.slice(8, 10);
let createdMonth = value.item.created_at.slice(5, 7);
let createdYear = value.item.created_at.slice(2, 4);
function dateSelected() {
if (
month === createdMonth &&
year === createdYear &&
(day === '00' || day == createdDay)
) {
console.log('foi dateSelected');
const [on, setOn] = useState(false);
const Details = (on: any) => {
if (on === true) {
return (
<View style={styles.infos}>
<Text style={styles.TextInfos}>
{' '}
CPF/CNPJ: {value.item.cpf_cnpj}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Criado em: {value.item.created_at}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Endereço da carteira: {value.item.address}{' '}
</Text>
<Text style={styles.TextInfos}> Valor: {value.item.amount}BTC </Text>
</View>
);
} else {
return <View />;
}
};
return (
<View>
<TouchableOpacity
style={styles.card}
onPress={() => setOn(oldState => !oldState)}>
<View style={styles.dateStyleView}>
<Text style={styles.dateStyle}>{createdDay}</Text>
</View>
<View style={styles.left}>
<Text style={styles.title}>Venda rápida</Text>
<Text style={styles.semiTitle}>
{
{
0: 'Pendente',
1: 'Aguardando conclusão',
2: 'Cancelado',
100: 'Completo',
}[value.item.status]
}
</Text>
</View>
<View style={styles.right2}>
<Text style={styles.price}>R$ {value.item.requested_value}</Text>
</View>
</TouchableOpacity>
<View>{Details(on)}</View>
</View>
);
}
}
return dateSelected();}
and i call it here
return (
<ScrollView>
...
<View style={styles.center}>
...
<View style={styles.middle2}>
...
<FlatList
extraData={[refresh, newMonths]}
data={newMonths}
renderItem={(item: any) => <Item value={item} />}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={15}
/>
</View>
</View>
</ScrollView>);}
The scroll bar in the right, start to increase until renders all transactions from the data:
App scroll bar

Resources