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