为什么我的组件在 onChange 而不是 onClick 时重新呈现?

Why does my component rerender at onChange and not onClick?

我想弄清楚为什么我的组件仅在我在我的文本字段上键入时才重新呈现(我猜它会因为 onChange 部分而改变?)在我制作 API 调用我的 onClick 按钮

下面是我的代码:

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

这是我的 API 电话:

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

API 调用 return 正是我想要的,我也将其重新格式化为我想要的方式。

我只是想让我的组件重新渲染到最新的搜索结果并在onClick之后重新渲染;不是当我再次开始在文本字段中输入时,它会呈现出我上次的搜索结果。

谢谢

问题

代码未正确重新呈现,因为 GoogleBookSearch 代码未正确等待填充 bookDetails 数组。我怀疑这是由于嵌套了 result.json() 承诺链而不是 return 造成的。换句话说,await fetch(....).then((result) => { ... }) Promise 已解决,GoogleBookSearch 函数的其余部分 运行 和 return 编辑了一个空 bookDetails 数组 before 嵌套的 result.json() Promise 链解析并随后改变 bookDetails 数组。

这就是为什么单击搜索按钮不会触发重新呈现,但稍后当您再次输入搜索输入时,它触发重新呈现并且您会查看突变的 bookList 状态。

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

解决方案

不要嵌套 Promise 链,创建 Promise 是为了消除“嵌套地狱”。您 可以 return 嵌套的 Promise 并保持 Promise 链扁平化。

不过也不要将 async/await 与 Promise 链混合使用。将 try/catchasync/await 代码一起使用,并使代码看起来“同步”。

示例:

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