我有一个问题,我无法使用React的useState
来解决。
我正在尝试发出电影和分页的API请求,但是我的状态之一是undefined
,我真的不明白为什么。
我的组件是功能组件。
这是我的useState
:
const [state, setState] = useState({
films: [],
page: 1,
genres: [],
currentCategory: ""
})
这是我的API请求:
const getFilms = () => {
Axios.get(`${baseUrlDiscover}&page=${state.page}&with_genres=${state.currentCategory}`)
.then(response => {
setState({
films: response.data.results
});
})
.catch(error => {
console.log(error);
setState({
films: []
});
});
}
调用getFilms()
来安装组件。
useEffect(() => {
getFilms()
}, [])
然后我用.map
显示收到的电影:
const theMovies = state.films.map((film, idx) => {
return <Film film={film} key={idx} />
});
这时,我在第一页上收到20部电影,但是当我在控制台中登录page state
以获取第二页时,我收到了undefined
。
我想使用下面的这段代码来获取20部电影的第二页,但是如果没有page state
,我会收到类似state.films.map is undefined
const btnClickNext = (e) => {
if (state.films && state.page !== 500) {
setState(
prevState => ({ page: (prevState.page += 1) }),
getFilms
);
}
getFilms();
}
在return
中,我调用btnClickNext
函数:
<Button
href=""
target="_blank"
onClick={() => btnClickNext()}
>
Next <FaChevronRight />
</Button>
您是否知道问题可能来自何处?以及为什么console.log(state.page)
给出undefined
吗?
非常感谢您
答案 0 :(得分:0)
实际上,这不是您应该使用useState的方式 您应该像这样创建单独的useState
const [films, setFilms] = useState([]),
const [page, setPage] = useState(1),
const [genres,setGenres] = useState([]),
const [currentCategory, setCurrentCategory] = useState("")
您需要更新这些状态时,可以相应地调用各自的设置器。
答案 1 :(得分:0)
请参阅文档中的this note:
注意
与类组件中的
setState
方法不同,useState
不会自动合并更新对象。您可以通过将函数更新程序形式与对象传播语法结合在一起来复制此行为:setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
另一个选项是
useReducer
,它更适合于管理包含多个子值的状态对象。
在当前状态下,使用单个对象表示所有状态,仅在更新一项时就必须分散状态的其他方面。因此,例如:
// Incorrect
setState({
films: []
});
你会做的:
// Incorrect
setState(state => ({
...state,
films: []
}));
请勿执行以下操作:
// DON'T DO THIS, IT'S LIKELY TO CAUSE TROUBLE
setState({
...state,
films: []
});
状态更新可以是异步的,也可以堆叠在一起。如果这样做,则可能会清除以前的更新的效果。
但是,我建议您阅读this entire page,而不要使用单个状态对象。从根本上讲,用钩子代替:
// Not this
const [state, setState] = useState({
films: [],
page: 1,
genres: [],
currentCategory: ""
})
您通常希望这样做:
// This instead
const [films, setFilms] = useState([]);
const [page, setPage] = useState(1);
const [genres, setGenres] = useState([]);
const [currentCategory, setCurrentCategory] = useState("");
然后使用适当的setXYZ
函数。而是使用setState({films: []})
,您可以这样做:
setFilms([]);
在评论中您说过:
我为每个元素创建了一个useState,最后得到了正确的元素。我的函数增加了页面数,但是我无法从这些页面中获取电影。
由于为它提供了一个空的依赖项数组,因此useEffect
回调仅在首次创建组件时运行一次。如果要在某些更改(例如page
更改)时再次调用效果回调,请将其添加到依赖项数组。 getFilms
似乎同时使用了page
和currentCategory
,因此您将两者都包括在内。我还将在getFilms
中为它们设置参数,而不是从状态中使用它们:
useEffect(() => {
getFilms(page, currentCategory); // ***
}, [page, currentCategory]); // ***
现在,当page
或currentCategory
发生更改时,回调将被调用(但是不用担心,如果您一次又一次地更改它们,则通常只调用一次)。
如果您要开始新的GET
,也可以考虑取消未完成的getFilms
。 (或者至少,至少不考虑其结果。)
有关详情,请参见the axios documentation。从文档看来,它使用的是旧的,现已撤消的可撤销的承诺提案。
这是接受取消令牌的const getFilms = (page, currentCategory, cancelToken) => {
return Axios.get(
`${baseUrlDiscover}&page=${page}&with_genres=${currentCategory}`,
{ cancelToken }
);
};
版本:
useEffect(() => {
let cancel;
let cancelToken = new Axios.CancelToken(c => {
cancel = () => {
c();
c.cancelled = true;
};
});
getFilms(page, currentCategory, cancelToken)
.then(films => setFilms(films))
.catch(error => {
if (!cancel.cancelled) {
// ...handle/report error, then:
setFilms([]);
}
});
return cancel;
}, [page, currentCategory]);
然后您将像这样使用它:
getFilms
笨拙,也许有一种更好的特定于Axios的方式(我不使用Axios)。 FWIW,这是const getFilms = (page, currentCategory, signal) => {
return fetch(
的现代版本,使用了fetch
和AbortSignal
:
${baseUrlDiscover}&page=${page}&with_genres=${currentCategory}
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
getFilms(page, currentCategory, signal)
.then(films => setFilms(films))
.catch(error => {
if (!signal.aborted) {
// ...handle/report error, then:
setFilms([]);
}
});
return () => controller.abort();
}, [page, currentCategory]);
,
{信号}
);
};
然后您将像这样使用它:
useEffect
我建议在useEffect
上使用this article(这实际上比{{1}}还要多,它涉及使用挂钩的功能组件如何工作)。