Firebase only fetches data on second or third attempt - javascript

I'm building an app with React and Firestore.
In one feature, I need to use some specific user data to query data from another collection, and show that on the app.
Specifically I want to use users.books, which returns an array, to query the collection books.
However, for some reason the users.books doesn't load on first render. It typically takes 2-3 renders to fetch the books.user data. This is despite the currentUserUID being loaded right away.
I've tried using a loading state as specified in How to wait for Firebase data to be fetched before progressing?, but to no avail.
Do I need to use the onSnapShot method?
Thanks for reading
My code
import 'firebase/firestore'
import { booksRef} from '../../App';
const ProfileScreen = ({ navigation }) => {
const currentUserUID = firebase.auth().currentUser.uid;
const [firstName, setFirstName] = useState('');
const [userBookTitles, setUserBookTitles] = useState([]);
const [userBooks, setUserBooks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function getUserInfo(){
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
setFirstName(dataObj.firstName)
setUserBookTitles(dataObj.books)
console.log(userBookTitles)
}
}
getUserInfo();
}, [])
useEffect(() => {
async function getUserBooks() {
booksRef.where("title", "in", userBookTitles).onSnapshot(snapshot => (
setUserBooks(snapshot.docs.map((doc) => ({id: doc.id, ...doc.data()})))
))
}
setLoading(false);
getUserBooks()
}, [])
if (!loading) {
return (
<View style={styles.container}>
<Text> Hi {firstName} </Text>
<TouchableOpacity onPress={handlePress}>
<Text> Log out </Text>
</TouchableOpacity>
<Row
books={userBooks}
/>
</View>
);
} else {
return (
<View style={styles.container}>
<Text> Test </Text>
</View>
);
}
};

So it's worth noting that your setX methods may, or may not, complete in the sequence you have them in your code. Therefore, your booksRef call could be being made even though userBookTitles is an empty array. Which would explain why you're not getting any data on load.
You're setting userBookTitles in your first useEffect and the only other place I see you're using it is in your booksRef call. One easy fix would be to simple move booksRef inside the else statement of the first useEffect and simply pass it the userBookTitles there. This should help in solving your issue, if I understood it correctly.

Related

Fetched data from database not displaying in FlatList

Exactly as the title says I am fetching data from a database and trying to display the stored image URI as an image in my React Native application. I'm not entirely sure where the process is going wrong, but my array is being filled and nothing is being shown. I have tried hardcoding the data that's being fetched into my application and it is being displayed then. I have wrapped my view statement that the flatlist is nested into with the following: {arrayName && .... }. Here is some relevant code. The Test field appears, but the flatlist will not.
const [data, setData] = React.useState([]);
const userImgData = [];
{userImgData && <View style={{width: 300, height: 300}}>
<FlatList
data={userImgData}
keyExtractor={(item) => item.id}
key={item=> item.id}
renderItem={({ item }) => (
//<Text style={styles.headerText}>{item.id}</Text>
<Image source={{ uri: item.imageURI}} style={{ width: 200, height: 200 }} />
)}
/>
<Text>Test</Text>
</View>}
My array is set here...
function getData(){
axios.get('IP/imagefiles')
.then((response) => {
const myObjects = response.data;
setData(myObjects);
});
//console.log(data);
for(let i = 0; i < data.length; i++){
if(data[i].user == user){
userImgData.push(data[i]);
}else{
console.log('no data found!');
};
};
console.log(userImgData);
};
Here is how the data is coming across...
Edit: Changed userImgData to a state variable and got some results to show.
There appears to be a couple of issues present...
You're trying to loop over data before it is assigned a value. This is because the Axios request is asynchronous so the code after the request runs before the code in the then() callback.
userImgData isn't a state variable and even if it was, using push() won't trigger a state update and won't re-render your component.
Try using a memo hook to provide the filtered userImgData instead
const [data, setData] = useState([]);
const userImgData = useMemo(
() => data.filter((d) => d.user == user),
[data, user]
);
const getData = async () => {
setData((await axios.get("IP/imagefiles")).data);
};
You can check userImgData.length to conditionally render the list.

How to pass an async object to a props in react?

I'm practicing react and how to incorporate react to firebase/firestore. Now I want to make something like a CMS using firestore database. I have successfully fetch data from the database and confirmed it by console log. But the problem is whenever I tried to pass the object to be used as my props it throws me an error. But if I add the code after the page is fully loaded, the data is successfully loaded and throws me an error after I refresh the page.
I think it has something to do with asynchronous request but I don't know how to pull it off.
const app = initializeApp(firebaseConfig)
const db = getFirestore(app);
const [data, setData] = useState({});
const fetchData =async () => {
const docRef = doc(db, "data", "RSVp8ljO95Dpwa0oSs0G");
const docSnap = await getDoc(docRef);
const dataTest = docSnap.data();
await setData(dataTest);
console.log("Document data:", dataTest);
}
useEffect(() => {
fetchData();
}, [])
return (
<div style={{overflow: 'hidden'}}>
<NavBar />
<div style={{width: '100%', height:'81vh', padding: '5%', overflow:'scroll'}}>
<Container >
<div className="d-flex flex-wrap justify-content-center">
<TrainingSchedule
**date={data.event1.date} //I want to pass the object here but throws me an error**
month='SEPT'
eventTitle='Lets Get to know each other'
eventDescription='Have a drink with our finest coach and enjoy the summer'
time='1pm'
backgroundColor= 'CadetBlue'
/>
......
Hi #Juls: Welcome to StackOverflow.
In your example, you're trying to access properties on data that don't exist in the first render (before the effect hook runs). That's why you're getting the error. Instead, check to make sure the object and properties that you need exist before trying to access them:
<script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.15.7/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useEffect, useState} = React;
async function getDataFromAPI () {
await new Promise(res => setTimeout(res, 1000));
return { event1: { date: '2021-09-18' } };
}
function Example () {
// Leave the state value undefined instead of initializing it as an empty object
const [data, setData] = useState();
useEffect(() => {
const fetchData = async () => {
const fetchedData = await getDataFromAPI();
setData(fetchedData);
};
fetchData();
}, []);
return (
<div>
{
// Check if the data exists before trying to access its properties:
data
? (<div>{data.event1.date}</div>)
: (<div>Data is being fetched...</div>)
}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
please provide us with the error so that we can fully understand the problem.
but for now, try this :
date={data.event1.date || null}
this should cause the date to become null if the data is not ready, then get updated in the next render which is caused by setState.

React Native Firebase fetch data

I'm trying to fetch data from firebase.
I want to push the values in an array and map through it in a view.
The only problem is that i can see the values in my array but not outside the .then function.
const dataArray = []
firebase
.database()
.ref("Challenges/Day")
.once("value")
.then((snapshot) => {
snapshot.forEach((child) => {
dataArray.push(child.val());
});
console.log(dataArray)
});
return (
<View style={styles.text}>
{dataArray.map((data) => {
<Text>{data}</Text>;
})}
</View>
);
if i console log it then the output is :
Array [
"eat",
"sleep",
]
But outside it's function my array is empty.
The issue here is console.log() is getting executed even before the data is returned from the firebase as response. When the request is made it takes sometime (say 1 sec) to return the response, but in the meantime the control executes the next line which is console.log(). When this line is executed, the value of dataArray=[] at that amoment. So for your case it is best to keep dataArray as state variable, so when the response is returned you can set the value of the dataArray which will re-render your component, thus displaying its return value.
const [dataArray,setDataArray] = useState([])
firebase
.database()
.ref("Challenges/Day")
.once("value")
.then((snapshot) => {
let tempArray=[];
snapshot.forEach((child) => {
tempArray.push(child.val());
}
setDataArray(tempArray);
);
console.log(dataArray)
});
return (
<View style={styles.text}>
{dataArray.map((data) => {
<Text>{data}</Text>;
})}
</View>
);
It's due to 2 issues you are facing.
asynchrounous - means what you are seeing in UI is the [] before firebase is executed.
state update - there were no state updated, hence even after the variable change occurred in firebase, the UI is not updated. Any normal variable changes will not re-render the UI, only a *state change will trigger a re-render in React.
What you can do.
const [dataArray, setDataArray] = useState([])
useEffect(() => {
firebase
.database()
.ref("Challenges/Day")
.once("value")
.then((snapshot) => {
snapshot.forEach((child) => {
//call an update to the array.
//setDataArray((oldDataArray) => oldDataArray.push(child.val()))
setDataArray((oldDataArray) => [...oldDataArray, child.val()])
});
console.log(dataArray)
});
}, []); //will only initialize once(on mount). Putting outside will get called again and again when state change.
return (
<View style={styles.text}>
{dataArray.map((data) => {
<Text>{data}</Text>;
})}
</View>
);

Call and API inside useEffect in react native?

I'm calling an Api to get data but the data is really heavy. I'm wondering if i'm calling it in right place inside useEffect or should i call it somewhere else. I've put the console.log to check but the number of console.log exceeded the number of objects i have in the API. My code is :
const ProductsList = () => {
const [products, setProducts] = useState([]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
getProducts().then((response) => {
if (isMounted) {
console.log('im being called');
setProducts(response);
setLoading(false);
}
});
return () => { isMounted = false; };
}, [products]);
return (
<View style={styles.container}>
{isLoading ? <ActivityIndicator /> : ((products !== [])
&& (
<FlatList
data={products}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<Item
style={{ marginLeft: 35 }}
name={item.name}
date={item.date}
address={item.adress}
/>
);
}}
/>
)
)}
</View>
);
};
It looks like your effect goes round in a circle:
On each render the effect will look at products to see if it has changed.
If it has changed it will call your effect which fetches new products.
When you get new products you update your products state.
This causes the effect to run again.
You probably only want to run that effect once, when the component mounts. In which case you can simply write:
useEffect(() => {
getProducts().then((response) => {
setProducts(response);
setLoading(false);
});
}, []); // <-- note the empty array, which means it only runs once when mounted
If that single API call is too heavy, then you need to look more at things like pagination in your requests, or modifying the response to return only the data you really need. But that's outside the scope of this question, I think.
Let me know if you have any questions.

Handling async funtion of Firebase

So i'm using firebase as my database for my mobile app that i create with react native. I once use it in a web app, and i remember that there is some part of firebase that is async function. Now when i want to use firebase in react, i meet the same problem, and i forgot how to solve it.
function viewName(userId) {
firebase.database().ref('player/' + userId).on('value', (snapshot) => {
const userName = snapshot.val.name;
return userName;
});
}
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Text>{viewName('0')}</Text>
</View>
);
}
It's only a mock app, not the real app. I'm trying to receive the name of user with key "0" (player/0/name), then show it as on the app screen. But as u can guess,the viewName() function return nothing...
Can someone help me?
(Sorry if it's sounds stupid or if my question make no sense. First time asking here, and first time using react)
function viewName(userId, cb) {
firebase.database().ref('player/' + userId).on('value', (snapshot) => {
const userName = snapshot.val.name;
cb(userName);
});
}
export default function App() {
const [username, setUsername] = React.useState('')
React.useEffect(() => {
viewName('0', setUsername)
}, [])
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Text>{username}</Text>
</View>
);
}

Categories