Im trying to figure out why does my component re-render only when I am typing on my textfield (Im guessing it changes because of the onChange part? ) where it does not re-render after I made the API call on my onClick button
The below is my code:
export default function HomePage() {
const [searchBook, setSearchBook] = useState('');
const [bookList, setBookList] = useState([]);
return(
<Box>
<Box className='home-page-right-search-box' display='flex' flexDirection='row' alignItems='center' sx={{}}>
<Box flex={3} sx={{ fontSize: '75px' }}>
Explore
</Box>
<Box className='home-page-right-search-textfield' flex={1} display='flex' flexDirection='row'>
<TextField id="outlined-basic" label="🔍 Search" variant="outlined" sx={{ boxShadow: '0px 0px 10px 2px #C0C0C0', width: '350px', mr: 2 }} onChange={(value) => {
setSearchBook(value.target.value)
}} />
<Button sx={{ color: '#364d8a' }} onClick={
async () => {
const response = await GoogleBookSearch(searchBook);
setBookList(response);
}}>
Search
</Button>
</Box>
</Box>
<Box className='home-page-right-trending-container' sx={{ mt: 10, px: 2 }}>
<Box sx={{ mb: 5, fontSize: '35px' }}>
Trending
</Box>
<Grid container spacing={5}>
////*****This is the place where it is not re-rendering after onClick******///
{bookList.map((book) => {
console.log('book: ', book)
return (
<Grid item >
<Box className='home-page-right-trending-list-item' textAlign='center' sx={{ mt: 3, mr: 2 }} >
<Box className='trending-list-item-img-box'>
<img className='trending-list-item-img' src={book.imageLink} alt='book image' />
</Box>
<Box className='trending-list-item-title' sx={{ mt: 1, fontSize: '20px', color: 'grey', width:'200px' }}>
{book.title}
</Box>
<Box sx={{ mt: 1, fontSize: '15px', color: 'grey' }}>
{book.author}
</Box>
<Box sx={{ mt: 1, fontSize: '20px' }}>
⭐️ {book.rating}
</Box>
</Box>
</Grid>
);
})}
</Grid>
</Box>
</Box>
);
}
This is my API call:
export async function GoogleBookSearch(name) {
const bookDetails = [];
try {
await fetch(`https://www.googleapis.com/books/v1/volumes?q=${name}`).then((result) => {
result.json().then((item) => {
const itemsFirstTen = item['items'].slice(0, 10);
for (var i in itemsFirstTen) {
// console.log( 'sales info: ', itemsFirstTen[i]['saleInfo']);
bookDetails.push({
title: itemsFirstTen[i]['volumeInfo']['title'],
subtitle: itemsFirstTen[i]['volumeInfo']['subtitle'],
authors: itemsFirstTen[i]['volumeInfo']['authors'],
description: itemsFirstTen[i]['volumeInfo']['description'],
price: itemsFirstTen[i]['saleInfo']['saleability'] === 'FREE' || 'NOT_FOR_SALE' ? 0 : itemsFirstTen[i]['saleInfo']['listPrice']['amount'],
imageLink: itemsFirstTen[i]['volumeInfo']['imageLinks']['thumbnail'],
})
}
})
});
// return response;
} catch (error) {
console.log('error searching book: ', error);
}
console.log('bookdetails: ', bookDetails)
return bookDetails;
}
The API calls return exactly what I want and I've reformated it to the way I want it as well.
I just want my component to re-render to the latest search result and re-renders after onClick; not when I start typing again in the textfield then it renders out what my last search result was.
THanks
The code wasn't rerendering correctly because the GoogleBookSearch
code wasn't correctly waiting to populate the bookDetails
array. I suspect it's due to nesting the result.json()
promise chain and not returning it. In other words, the await fetch(....).then((result) => { ... })
Promise resolved and the rest of the GoogleBookSearch
function ran and returned an empty bookDetails
array before the nested result.json()
Promise chain resolved and later mutated the bookDetails
array.
This is why clicking the search button wasn't triggering a rerender, but then later when you again typed in the search input, it would trigger a rerender and you'd see the mutated bookList
state.
export async function GoogleBookSearch(name) {
const bookDetails = [];
try {
await fetch(`https://www.googleapis.com/books/v1/volumes?q=${name}`)
.then((result) => {
// (1) Nothing returned from here on, so the outer Promise resolved
// (2) This start a new, nested Promise chain
result.json()
.then((item) => {
const itemsFirstTen = item['items'].slice(0, 10);
for (var i in itemsFirstTen) {
// (4) bookDetails array mutated!!
bookDetails.push({ ...book data... });
}
})
});
} catch (error) {
console.log('error searching book: ', error);
}
console.log('bookdetails: ', bookDetails);
// (3) bookDetails array returned
return bookDetails;
}
Don't nest Promise chains, Promises were created to eliminate "nesting hell". You could return the nested Promise and keep the Promise chain flattened.
Don't mix async/await
with Promise chains either though. Use a try/catch
with the async/await
code and keep the code looking "synchronous".
Example:
async function GoogleBookSearch(name) {
try {
const response = await fetch(
`https://www.googleapis.com/books/v1/volumes?q=${name}`
);
const result = await response.json();
return (result.items || []).slice(0, 10).map((book) => ({
title: book.volumeInfo.title,
subtitle: book.volumeInfo.subtitle,
authors: book.volumeInfo.authors,
description: book.volumeInfo.description,
price:
book.saleInfo.saleability === "FREE" || "NOT_FOR_SALE"
? 0
: book.saleInfo.listPrice.amount,
imageLink: book.volumeInfo.imageLinks.thumbnail
}));
} catch (error) {
console.log("error searching book: ", error);
}
}