I have an application that is essentially a search with dropdown filters. The idea is to make them seamless with no buttons. When a user reaches the bottom of the page, fetchBooks
is called again, except this time it adds on to the list instead of replacing it wholesale. Inversely, if a user types something else in or chooses a different dropdown option, the idea is to use all the selections (which are now parts of the state) to construct an API query with params and get a whole new set which can then increase in size as a user traverses it.
Upon render, there are several state variables that are created via useState()
.
const bookTitle, setBookTitle = useState()
const isFiction, setIsFiction = useState()
I'm using our homegrown component library to implement the dropdowns but they have simple controls - onChange, initialValue - nothing crazy.
I'm using something similar to detect if a user reaches the end of the list:
useEffect(() => {
const onScroll = function () {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
fetchBooks(true) // true indicates we want to add to the list
}
}
window.addEventListener('scroll', onScroll)
return () => window.removeEventListener('scroll', onScroll)
}, [])
The problem is that whenever fetchBooks
is called the second time around, all of the values of dropdowns stored in state are undefined
. I could try to modify the code not to identify too much but that'd be a bit of a project.
My question is - does this ring any bells? Why would a function that exists in the same context of one component see stored state variables as undefined
?
Edit:
Here's what fetchBooks
looks like. I can't copy paste production code but I'll do my best not to skip anything important:
const fetchBooks = (updating=false) {
setLoadingIndicator(true);
// if updating is true, add to list with a different `start` in the url
// otherwise, start from the beginning of DB list
if (updating) {
searchOffset += 100
} else {
searchOffset = 0;
}
// think stringbuilder
const fetchUrl = new URL(BASE_URL);
if (isFiction)
fetchUrl.searchParams.set('isFiction', true)
if (searchOffset)
fetchUrl.searchParams.set('start', searchOffset)
// ... few more fields corresponding directly to state variables that "add on" to the URL
// this works, but only the first time this function runs. `isFiction` is undefined when this function is called later
fetch(fetchUrl)
.then(function (response) {
// handle non-200 code
return response.json()
})
.then(function(data) {
if (updating) {
setBookList(oldBookList => [...oldBookList, ...data.books])
} else {
setBookList(data.books);
}
setLoadingIndicator(false);
})
.catch((e) => {
console.error(e);
setLoadingIndicator(false);
})
}
Second time this function runs, fetchUrl defaults to BASE_URL and the state variables from dropdowns (in this case, isFiction
) is undefined
. This function doesn't seem like it'd be the cause of the problem.
Edit 2:
There is a useEffect
hook that looks like this:
useEffect(() => {
fetchBooks();
}, [isFiction])
Again, pretty innocent unless I'm missing something.