I have React component :
import { Hotels } from "./Hotels";
import WelcomePage from "./WelcomePage";
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.0/firebase-app.js";
import {
getFirestore,
collection,
getDocs,
addDoc,
} from "https://www.gstatic.com/firebasejs/9.6.0/firebase-firestore.js";
import {
getAuth,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
onAuthStateChanged,
signOut,
} from "https://www.gstatic.com/firebasejs/9.6.0/firebase-auth.js";
import { firebaseConfig, app, db, auth } from "../firebaseConfig";
import { useState } from "react";
function MainPage() {
const [hotels, setHotels] = useState([]);
const [authentication, setAuthentication] = useState(false);
async function fetchHotels() {
const _hotels = [];
const querySnapshot = await getDocs(collection(db, "reviews"));
querySnapshot.forEach((doc) => {
_hotels.push(doc.data());
});
console.log("fetched!");
setHotels(_hotels);
}
function isAuthenticated() {
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
const uid = user.uid;
setAuthentication(true);
} else {
// User is signed out
setAuthentication(false);
}
});
}
isAuthenticated();
fetchHotels();
return (
<main className="content">
<Hotels hotels={hotels} />
</main>
);
}
export default MainPage;
After the application starts, the fetchHotels
function starts to be called endlessly (this is evidenced by console.log("fetched!")
).
Under the same conditions, in other components, other functions are called adequately.
CodePudding user response:
You're calling fetchHotels
at the top level of your function component, so it's called on every render. In fetchHotels
, you eventually call setHotels
with a new array, which causes a re-render (since the new array by definition is different from the current one). So when that render happens, it calls fetchHotels
again, which eventually calls setHotels
again, which causes...
You need to only call fetchHotels
at appropriate times. For instance, it looks like you only need to do that when the component first mounts, in which case you'd do it inside a useEffect
callback with an empty dependency array (so that it is only run on component mount). And since nothing else calls it, you can just do the fetch right there in the callback:
useEffect(() => {
let cancelled = false;
(async () => {
try {
const snapshot = await getDocs(collection(db, "reviews"));
if (!cancelled) {
const hotels = snapshot.map(doc => doc.data());
setHotels(hotels);
console.log("fetched!");
}
} catch(error) {
// ...handle/report error
}
})();
return () => {
// Flag so we don't try to set state when the component has been
// unmounted. Ideally, if `getDocs` has some way of being cancelled
// (like `AbortController`/`AbortSignal`), do that instead; using
// a flag like this doesn't proactively stop the process.
cancelled = true;
};
}, []);
Note I added error handling; don't let an async
function throw errors if nothing is going to handle them. Also, I used map
to more idiomatically build an array (hotels
) from another array (snapshot
).
(You have the same basic problem with isAuthenticated
. The only reason it doesn't cause an infinite loop is that it calls setAuthentication
with a boolean value, so the second time it does that, it's setting the same value that was already there, which doesn't cause a re-render.)
CodePudding user response:
You can't invoke functions in a component like that. What you need to do is invoke it in useEffect and (optional) give it a parameter that triggers the useEffect function.