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”不等于“想读”
您好,我正在创建一个应用程序,用户可以在其中搜索图书并将其放在书架上,具体取决于用户点击的是哪个书架。目前,用户可以键入查询并显示许多结果。用户可以打开一本书的下拉菜单并点击书架(在下拉菜单中)以 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”不等于“想读”