useEffect 中的 React API 调用仅在硬编码参数时运行,而不是在使用状态时运行

React API call in useEffect runs only when parameter is hardcoded, not when using state

您好,我正在创建一个应用程序,用户可以在其中搜索图书并将其放在书架上,具体取决于用户点击的是哪个书架。目前,用户可以键入查询并显示许多结果。用户可以打开一本书的下拉菜单并点击书架(在下拉菜单中)以 select 该书的书架。

我想调用一个方法来更新书架。它仅在 shelfType 被硬编码时有效(shelfTypes 为 'wantToRead'、'read'、'currentlyReading')。我想要发生的是用户点击一个架子,该架子被设置为 SearchPage 中的本地状态变量 shelfType。然后,一旦 shelfType 发生变化,更新书架的方法将 运行(它对后端进行 API 调用)。

但是由于某些奇怪的原因,如果我将货架类型硬编码到更新方法中,我只能更新货架,而不是当我使用状态 shelfType 的值时。我究竟做错了什么?我希望这个问题是有道理的。

SearchPage.js

import React, { useEffect, useState } from 'react';
import { BsArrowLeftShort } from 'react-icons/bs';
import SearchBar from '../components/SearchBar';
import { search, update, getAll } from '../api/BooksAPI';
import Book from '../components/Book';

const SearchPage = () => {
  const [query, setQuery] = useState('');
  const [data, setData] = useState([]);

  const handleChange = (e) => {
    setQuery(e.target.value);
  };

  useEffect(() => {
    const bookSearch = setTimeout(() => {
      if (query.length > 0) {
        search(query).then((res) => {
          if (res.length > 0) {
            setData(res);
          } else setData([]);
        });
      } else {
        setData([]); // make sure data is not undefined
      }
    }, 1000);
    return () => clearTimeout(bookSearch);
  }, [query]);

  const [shelfType, setShelfType] = useState('None'); 
  const [currentBook, setCurrentBook] = useState({});

  const doSomethingWithBookAndShelf = (book, shelf) => {
    setShelfType(shelf);
    setCurrentBook(book);
  };

  useEffect(() => {
      //following line doesn't update like this, but I want it to work like this
      update(currentBook, shelfType).then((res) => console.log(res)); 
      // update works if I run update(currentBook, 'wantToRead').then((res) => console.log(res));
      getAll().then((res) => console.log(res));
  }, [shelfType]);

  return (
    <div>
      <SearchBar
        type="text"
        searchValue={query}
        placeholder="Search for a book"
        icon={<BsArrowLeftShort />}
        handleChange={handleChange}
      />
      <div className="book-list">
        {data !== []
          ? data.map((book) => (     
              <Book
                book={book}
                key={book.id}
                doSomethingWithBookAndShelf={doSomethingWithBookAndShelf}
              />
            ))
          : 'ok'}
      </div>
    </div>
  );
};

export default SearchPage;

Book.js

import React from 'react';
import PropTypes from 'prop-types';
import ButtonDropDown from './ButtonDropDown';

const Book = ({ book, doSomethingWithBookAndShelf }) => {

  return (
    <div className="book">
      <img
        src={book.imageLinks.thumbnail}
        alt={book.title}
        className="book-thumbnail"
      />
      <ButtonDropDown
        choices={['Currently Reading', 'Want to Read', 'Read', 'None']}
        onSelectChoice={(choice) => {
          // book came from the component props
          doSomethingWithBookAndShelf(book, choice);
        }}
      />
      <div className="book-title">{book.title}</div>
      <div className="book-authors">{book.authors}</div>
    </div>
  );
};

Book.propTypes = {
  doSomethingWithBookAndShelf: PropTypes.func.isRequired,
  book: PropTypes.shape({
    imageLinks: PropTypes.shape({
      thumbnail: PropTypes.string.isRequired,
    }),
    title: PropTypes.string.isRequired,
    authors: PropTypes.arrayOf(PropTypes.string),
  }).isRequired,
};

export default Book;

ButtonDropDown.js

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { BsFillCaretDownFill } from 'react-icons/bs';

const ButtonDropDown = ({ choices, label, onSelectChoice }) => {
  const [active, setActive] = useState(false);

  const toggleClass = () => {
    setActive(!active);
  };

  return (
    <div className="dropdown">
      <button
        type="button"
        className="dropbtn"
        onFocus={toggleClass}
        onBlur={toggleClass}
      >
        <BsFillCaretDownFill />
      </button>
      <div
        id="myDropdown"
        className={`dropdown-content ${active ? `show` : `hide`}`}
      >
        <div className="dropdown-label">{label}</div>
        {choices.map((choice, index) => (
          <button
            // eslint-disable-next-line react/no-array-index-key
            key={index}
            className="dropdown-choice"
            onClick={() => {
              // we create an specific callback for each item
              onSelectChoice(choice); 
            }}
            type="button"
            value={choice}
          >
            {choice}
          </button>
        ))}
      </div>
    </div>
  );
};

ButtonDropDown.propTypes = {
  choices: PropTypes.arrayOf(PropTypes.string).isRequired,
  label: PropTypes.string,
  onSelectChoice: PropTypes.func.isRequired,
};

ButtonDropDown.defaultProps = {
  label: 'Move to...',
};

export default ButtonDropDown;

因为您“想要阅读”选项中的文字不同

   choices={['Currently Reading', *'Want to Read'*, 'Read', 'None']}

基于此 // update works if I run update(currentBook, 'wantToRead').then((res) => console.log(res));

“wanToRead”不等于“想读”