'React Native Firebase update works but hangs app and closes modal
I am attempting to enable a Thumbs Up option where when pressed it retrieves the current number of thumbs up in my database for an item, increments it, then updates it. As you can see I've put 2 "here" console.logs, both log to the screen. I get no errors or catches, and the database updates (immediately when pressed). So it technically works, but then there is a lag/hang, for probably 7 seconds where the thumb icon shows as pressed the whole time like it's stuck, and then the modal goes away back to the main screen. As if it "crashed". If I take the update out, there is no hang and the modal remains open until I choose to close it. So it has to be that code. Do you see any glaring errors in my logic?
Update: I've traced the issue possibly to the screen that renders this component. I populate a flatlist using the firebase onValue, of which each object has this "thumbs up" feature. When I take out the onValue section and replace it with a single dummy data object instead, the thumbs up works perfectly. So there must be a problem with the listener maybe not unsubscribing right the way I have it. Previous screen code added.
import React, {useState} from "react";
import { Image, StyleSheet, TouchableOpacity, View } from "react-native";
import AppText from "../components/AppText";
import * as Sentry from 'sentry-expo';
import { ref, child, get, query, onValue, update } from "firebase/database";
function HotDidItWork({indexStore, db, auth}) {
var user = auth.currentUser;
if (!user){
return (
<></>
)
}
const [upPressed, setUpPressed] = useState(false);
const [displayText, setDisplayText] = useState('');
async function didPressUp(){
setUpPressed(true);
const dbRef = ref(db, 'deals/' + indexStore);
if (displayText == ''){
get(query(dbRef))
.then((usersSnapshot)=> {
if (usersSnapshot.exists()) {
let thumbsUpCount = usersSnapshot.val().thumbsUp;
thumbsUpCount = parseInt(thumbsUpCount);
thumbsUpCount += 1;
console.log('here 1');
update(dbRef, {thumbsUp: thumbsUpCount.toString()})
.then(()=> {
console.log('here 2');
setDisplayText('Thanks!');
})
.catch((e)=> {
console.log('error 1: ' + e.message);
})
} else {
console.log("No data available");
}
})
.catch((e)=> {
console.log("error 2: " + e.message);
})
}
}
return (
<View>
<View style={styles.messageContainer}>
<AppText style={upPressed == false && downPressed == false ? styles.didItWork : styles.pressed}>Did this deal work for you?</AppText>
<AppText style={downPressed == true || upPressed == true ? styles.didItWork : styles.pressed}>{displayText}</AppText>
</View>
<View style={styles.thumbs}>
<TouchableOpacity onPress={() => didPressUp()}>
<Image
style={styles.thumbsUp}
source={require("../assets/thumbsUp.png")}
/>
</TouchableOpacity>
</View>
</View>
);
}
export default HotDidItWork;
Previous Screen that renders flatlist of obj with thumbs up feature (I now believe this is where the error is):
import React, { useState, useEffect } from "react";
import {
FlatList,
Image,
ImageBackground,
Platform,
SafeAreaView,
StyleSheet,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import HotCardFav from "../components/HotCardFav";
import { ref, child, get, query, onValue, update } from "firebase/database";
import ListItemSeparator from "../components/ListItemSeparator";
import CardItemDeleteAction from "../components/CardItemDeleteAction";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { ActivityIndicator } from "react-native";
import {db, auth} from '../../src/config.js';
import AppText from "../components/AppText";
import colors from "../config/colors";
import * as Sentry from 'sentry-expo';
import Header from '../components/Header';
import { ThemeProvider, useFocusEffect } from '@react-navigation/native';
let initialMessagesFav = [];
let listViewRef;
function FavoritesHotScreen() {
var user = auth.currentUser;
if (user == null) {
return (
<></>
)
}
const [loading, setLoading] = useState(false);
const [messagesFav, setMessagesFav] = useState(initialMessagesFav);
const [messagesFavHold, setMessagesFavHold] = useState(initialMessagesFav);
const [refreshing, setRefreshing] = useState(false);
useFocusEffect(
React.useCallback( () => {
async function fetchData() {
// You can await here
const response = await loadListings();
// ...
return () => response();
}
fetchData();
}, [])
);
async function lookupUser(){
const dbRef = ref(db, 'users/' + user.uid);
const usersSnapshot = await get(query(dbRef));
return usersSnapshot;
}
const loadListings = async () => {
let favs = [];
let favsArray = [];
updateInput('');
setLoading(true);
console.log('exists 2');
lookupUser()
.then((snapshot) => {
if (snapshot.exists()) {
favs = snapshot.child("favorites").val();
if (favs != null){
favsArray = favs.split(',');
}
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
snapshot.forEach((childSnapshot)=>{
let found = favsArray.find(function (element) {
return element == childSnapshot.val().indexStore;
});
if (found != undefined){
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().title,
postedDate: childSnapshot.val().postedDate,
desc: childSnapshot.val().desc,
indexStore: childSnapshot.val().indexStore,
})
checkMessages(testData);
setLoading(false);
}
})
})
.catch((error) => Sentry.Native.captureException('Error FavoritesScreen function loadListings 2 ' + error));
}
const renderItem = ({ item }) => (<HotCardFav
title={item.title}
desc={item.desc}
indexStore={item.id}
postedDate={item.postedDate}
/>);
function checkMessages(testData){
const filtered = testData.filter(country => {
return (country.title != 'NA')
})
setMessagesFav(filtered);
setMessagesFavHold(testData);
setLoading(false);
}
let messagesShow = messagesFav.sort((a, b) => {
const messageA = new Date(parseInt(a.postedDate));
const messageB = new Date(parseInt(b.postedDate));
let comparison = 0;
if (messageA > messageB) {
comparison = 1;
} else if (messageA < messageB) {
comparison = -1;
}
return comparison * -1;
});
return (
<SafeAreaView style={styles.wholeThing}>
<Header image={require('../assets/longlogo4.png')} />
<View style={loading ? styles.activity : styles.none}>
<ActivityIndicator animating={loading} size="large" color="#0000ff" />
</View>
<FlatList
data={messagesShow}
keyExtractor={(messagesShow) => messagesShow.id.toString()}
renderItem={renderItem}
ItemSeparatorComponent={ListItemSeparator}
contentContainerStyle={styles.messagesList}
refreshing={refreshing}
ref={(ref) => {
listViewRef = ref;
}}
/>
</SafeAreaView>
);
}
export default FavoritesHotScreen;
Solution 1:[1]
Ok I figured it out here's what I found in case it helps someone in the future. The problem was in my main screen loadlistings() function (that I posted as an edit). It uses onValue to retrieve firebase data which attaches a listener which means ANYTIME data on my database changes, it rerenders the flatlist, entirely. Which automatically closes my modal since it's defaulted to be closed when the screen starts. So by me pressing the "thumbs up", it was changing data, the listener responded, rerendered the flatlist with a closed modal. Maybe this should have been obvious but it wasn't for me. I fixed it by adding a simple "onlyOnce" flag to the end of the onValue per firebase documentation:
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
snapshot.forEach((childSnapshot)=>{
let found = favsArray.find(function (element) {
return element == childSnapshot.val().indexStore;
});
if (found != undefined){
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().title,
postedDate: childSnapshot.val().postedDate,
desc: childSnapshot.val().desc,
indexStore: childSnapshot.val().indexStore,
})
checkMessages(testData);
setLoading(false);
})
},{
onlyOnce: true
})
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
