I have my react js app linked to cloud Firestore and I'm trying to display the objects on my js file.
I have only 1 object in my Firestore but it keeps reading in a loop and i cant figure out why.
Code from explore.js (display objects from Firebase)
const [nft,setNft]=useState([])
const getNft= async()=>{
const nft = await fs.collection('NFT').get();
const nftArray=[];
for (var snap of nft.docs){
var data = snap.data()
data.ID = snap.id;
nftArray.push({
...data
})
if(nftArray.length===nft.docs.length){
setNft(nftArray);
}
}
}
useEffect(()=>{
getNft();
})}
{nft.length > 0 && (
<div>
<div className='cardContainer'>
<Nft nft={nft}/>
</div>
</div>
)}
{nft.length < 1 && (
<div className='loading'>Loading products..</div>
)}
Infinite loop console
useEffect has a "trigger" property.
ex:
Will run on every render
useEffect(() => {
//do something
});
Will run only once
useEffect(() => {
//do something
}, []);
Will run when given properties changes
useEffect(() => {
//do something
}, [someProperty, someOtherProperty]);
In your case, you are calling the async function, the async function updates the state, and causes a rerender. Since you don't have any trigger (or empty array) added to the useEffect, it will run again, and again, and again.
Related
I have an input. On every change to the input, I want to call an API.
Here's a simplified version of the code:
// updated by input
const [urlText, setUrlText] = useState("");
const fetcher = useFetcher();
useEffect(() => {
if (urlText === "" || !fetcher) return;
fetcher.load(`/api/preview?url=${urlText}`);
}, [urlText]);
The issue is, when I put urlText inside of the dependencies array, there is an infinite rendering loop, and React claims the issue is I might be updating state inside of the useEffect. However, as far as I can tell, I'm not updating any state inside of the hook, so I'm not sure why an infinite re-render is happening.
Any thoughts?
The fuller version of the code is:
Note: The bug still happens without the debounce, or the useMemo, all of that stuff is roughly irrelevant.
export default function () {
const { code, higlightedCode } = useLoaderData<API>();
const [urlText, setUrlText] = useState("");
const url = useMemo(() => getURL(prefixWithHttps(urlText)), [urlText]);
const debouncedUrl = useDebounce(url, 250);
const fetcher = useFetcher();
useEffect(() => {
if (url === null || !fetcher) return;
fetcher.load(`/api/preview?url=${encodeURIComponent(url.toString())}`);
}, [debouncedUrl]);
return (
<input
type="text"
placeholder="Paste URL"
className={clsx(
"w-full rounded-sm bg-gray-800 text-white text-center placeholder:text-white"
//"placeholder:text-left text-left"
)}
value={urlText}
onChange={(e) => setUrlText(e.target.value)}
></input>
);
}
The problem you're having is that fetcher is updated throughout the fetch process. This is causing your effect to re-run, and since you are calling load again, it is repeating the cycle.
You should be checking fetcher.state to see when to fetch.
useEffect(() => {
// check to see if you haven't fetched yet
// and we haven't received the data
if (fetcher.state === 'idle' && !fetcher.data) {
fetcher.load(url)
}
}, [url, fetcher.state, fetcher.data])
https://remix.run/docs/en/v1/api/remix#usefetcher
You might by setting state in useFetcher hook, please check code of load method from useFetcher.
Update: I'm silly. useDebounce returns an array.
I'm learning to use firebase and react. I have shared my firestore collection image. and my code for fetching the array from my document is given below.
This code is fetching the data from my firestore database and then storing the result in my watchlistMovies react state. when i try to log the react state or even data.data() it gives the desired result but when i try to map over the array or do something similar like logging watchlistMovies.myList[0].media_type it hits me with an error. i tried my best trying different things making it work but it breaks a thing or two in process.
I hope someone here will help me. Thank you in advance! : )
updated the code
const Watchlist = () => {
const [watchlistMovies, setwatchlistMovies] = useState([]);
const {currentUser} = useAuth()
const usersCollectionRef = collection(db,"users")
const docRef = doc(db,"users",currentUser.uid)
useEffect(() => {
const getWatchListMovies = async () => {
const data = await getDoc(docRef)
if (data.exists()) {
console.log(data.data());
setwatchlistMovies([...watchlistMovies ,data.data().myList])
} else {
console.log("empty");
}
}
getWatchListMovies();
}, [])
console.log(watchlistMovies);
// console.log(watchlistMovies.myList[0]);
return (
<div className="content-page-area">
<h1 className="trending-text"> My Watchlist </h1>
<Container className="watchlist-container">
<hr/>
{watchlistMovies.map(
(item) => (
<ListContent
item_poster={item.poster_url}
item_title={item.media_title}
item_year={item.release_year}
item_rating={item.media_rating}
item_type={item.media_type}
item_id={item.media_id}
/>
)
)}
</Container>
<br/>
<br/>
<br/>
</div>
)
}
export default Watchlist
I have a custom hook in my React application which uses a GET request to fetch some data from the MongoDB Database. In one of my components, I'm reusing the hook twice, each using different functions that make asynchronous API calls.
While I was looking at the database logs, I realized each of my GET requests were being called twice instead of once. As in, each of my hooks were called twice, making the number of API calls to be four instead of two. I'm not sure why that happens; I'm guessing the async calls result in re-renders that aren't concurrent, or there's somewhere in my component which is causing the re-render; not sure.
Here's what shows up on my MongoDB logs when I load a component:
I've tried passing an empty array to limit the amount of time it runs, however that prevents fetching on reload. Is there a way to adjust the custom hook to have the API call run only once for each hook?
Here is the custom hook which I'm using:
const useFetchMongoField = (user, id, fetchFunction) => {
const [hasFetched, setHasFetched] = useState(false);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
if (!user) return;
try {
let result = await fetchFunction(user.email, id);
setData(result);
setHasFetched(true);
} catch (error) {
setError(error.message);
}
};
if (data === null) {
fetchData();
}
}, [user, id, fetchFunction, data]);
return { data, hasFetched, error };
};
This is one of the components where I'm re-using the custom hook twice. In this example, getPercentageRead and getNotes are the functions that are being called twice on MongoDB (two getPercentageRead calls and two getNotes calls), even though I tend to use each of them once.
const Book = ({ location }) => {
const { user } = useAuth0();
const isbn = queryString.parse(location.search).id;
const { data: book, hasFetched: fetchedBook } = useFetchGoogleBook(isbn);
const { data: read, hasFetched: fetchedPercentageRead } = useFetchMongoField(
user,
isbn,
getPercentageRead
);
const { data: notes, hasFetched: fetchedNotes } = useFetchMongoField(
user,
isbn,
getNotes
);
if (isbn === null) {
return <RedirectHome />;
}
return (
<Layout>
<Header header="Book" subheader="In your library" />
{fetchedBook && fetchedPercentageRead && (
<BookContainer
cover={book.cover}
title={book.title}
author={book.author}
date={book.date}
desc={book.desc}
category={book.category}
length={book.length}
avgRating={book.avgRating}
ratings={book.ratings}
language={book.language}
isbn={book.isbn}
username={user.email}
deleteButton={true}
redirectAfterDelete={"/"}
>
<ReadingProgress
percentage={read}
isbn={book.isbn}
user={user.email}
/>
</BookContainer>
)}
{!fetchedBook && (
<Wrapper minHeight="50vh">
<Loading
minHeight="30vh"
src={LoadingIcon}
alt="Loading icon"
className="rotating"
/>
</Wrapper>
)}
<Header header="Notes" subheader="All your notes on this book">
<AddNoteButton
to="/add-note"
state={{
isbn: isbn,
user: user,
}}
>
<AddIcon color="#6b6b6b" />
Add Note
</AddNoteButton>
</Header>
{fetchedNotes && (
<NoteContainer>
{notes.map((note) => {
return (
<NoteBlock
title={note.noteTitle}
date={note.date}
key={note._noteID}
noteID={note._noteID}
bookID={isbn}
/>
);
})}
{notes.length === 0 && (
<NoNotesMessage>
You don't have any notes for this book yet.
</NoNotesMessage>
)}
</NoteContainer>
)}
</Layout>
);
};
The way you have written your fetch functionality in your custom hook useFetchMongoField you have no flag to indicate that a request was already issued and you are currently just waiting for the response. So whenever any property in your useEffect dependency array changes, your request will be issued a second time, or a third time, or more. As long as no response came back.
You can just set a bool flag when you start to send a request, and check that flag in your useEffect before sending a request.
It may be the case that user and isbn are not set initially, and when they are set they each will trigger a re-render, and will trigger a re-evalution of your hook and will trigger your useEffect.
I was able to fix this issue.
The problem was I was assuming the user object was remaining the same across renders, but some of its properties did in fact change. I was only interested in checking the email property of this object which doesn't change, so I only passed user?.email to the dependency array which solved the problem.
I have an array of objects that are suposed to pass as props to a element to render a list for each object, but when I try the code only one its rendered and the others are ignored even though I've console.log them and I can see them. Here's the code:
const mainFilterQueries = ['popular', 'top_rated', 'upcoming']
const sortByMovies = "movie"
const [moviesLists, setMoviesLists] = useState([])
useEffect(()=>{
createLists(mainFilterQueries, sortByMovies , setMoviesLists, moviesLists)
console.log(moviesLists)
}, [])
async function fetchData(query, sort, setMethod, state){
let listsCreated = []
try {
const response = await fetch(`https://api.themoviedb.org/3/${sort}/${query}?api_key=${apikey}`)
const data = await response.json();
let dataObject = {key:`${sort}-${query}`, data:data.results, title:`${query}`}
console.log(dataObject)
listsCreated.push(dataObject);
setMethod([...state, dataObject])
} catch (error) {
console.error(error)
}
}
function createLists(arr, sort, target, state){
arr.forEach(query =>{
fetchData(query, sort, target, state)
})
}
return (
<React.Fragment>
{moviesLists.map(list =>{
return(
<div>
<MoviesList dataList={list}/>
</div>
)
})}
</React.Fragment>
)
You're calling setMethod([...state, dataObject]) in a loop. state will never be updated until the next render, meaning you're actually calling the function as if it were like this: setMethod([...[], dataObject]) for every iteration.
Instead use the functional update form of setState like this:
setMethod((prev) => ([...prev, dataObject]))
The functional update ensures that the previous state is most up-to-date with any previous calls to setMethod.
See also why-calling-react-setstate-method-doesnt-mutate-the-state-immediately.
In my React.js code Im fetching from an API and I m getting this error
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
PLEASE WHAT AM I DOING WRONG?
Here's my fetch component in App.js
const App = () => {
const [records, setRecords] = useState([])
const [loading, setLoading] = useState({ loading: false })
useEffect(() => {
(async function() {
try {
setLoading(true)
const res = await fetch('http://api.enye.tech/v1/challenge/records?limit=20&skip=0')
const data = await res.json();
setRecords({ records: data})
setLoading(false)
console.log(data);
} catch (err) {
console.error(err);
}
})()
}, [])
if(loading) {
return <Preloader />
}
return (
<Fragment>
<SearchBar />
<div className='container'>
<Records loading={setLoading()} records={setRecords()} />
</div>
</Fragment>
);
}
And this is where I'm passing in the fetched data as props
const Records = ({ records, loading }) => {
return (
<div className={styles.p__container}>
<div className="row">
<div className="col-sm-8">
<div className="py-3">
{records.length} Records
</div>
</div>
</div>
<div className={styles.p__grid}>
{records.map(record => (
<RecordItem key={record.id} record={record} />
))
}
</div>
<div className={styles.p__footer}>
</div>
</div>
)
My integrated terminal shows no error but I get this error in my browser console
Im also trying to see if I can fetch just 20 profiles from the API instead of 100
http://api.enye.tech/v1/challenge/records
It's likely here, when you call setLoading() and setRecords on every render:
<Records loading={setLoading()} records={setRecords()} />
You probably just want to pass a the loading and records variables:
<Records loading={loading} records={records} />
The error is in the line
<Records loading={setLoading()} records={setRecords()} />
By writing setLoading() and setRecords() you are essentially changing the states loading, and records. This ensures a re-render, since states are being changed
I believe, you are trying to pass the current loading status and records array as props to component Records. To do so, you should pass the values like
<Records loading={loading} records={records} />
That should clear out the too many re-renders error
Suggestion on useState usage
You are initializing the states loading and setRecords in a different way than the way you are using it later.
For eg., you initialize loading as {loading: false} but later call setLoading(false). You should initialize the loading state as a boolean state using
const [loading, setLoading] = useState(false);
// example usage
setLoading(true);
Similarly, for records, you are initializing with an empty array useState([]), but later setting records state as an object using setRecords({ records: data})
You should use either one of the approaches
/** Approach 1 (Array typed) */
const [records, setRecords] = useState([]);
// example usage
setRecords(data);
/** Approach 2 (Object typed) */
const [records, setRecords] = useState({records: []});
// example usage
setRecords({records: data});
React has a property that whenever the value of states in a React component get updated, the component is re-rendered. Here you are passing the setLoading() and setRecords() as props to the Records component. You should pass records and loading instead. Every time setLoading() and setRecords() get updated, your component is re rendered and as a result an infinite loop of re rendering takes place.