为什么即使在 React JS 中更改状态后,我的代码也没有从 API 中获取更新的详细信息?
Why my code is not fetching updated details from API's even after changing state in React JS?
我正在使用两个组件:LandingPage 和 SearchMovie。 SearchMovie 组件正在更新 searchTerm (onChange) 并将其传递给从 API 获取电影的 LandingPage(父组件)。我签入 console.log 并且 SearchTerm 状态正在更新,但 LandingPage 未使用更新的 searchTerm 状态重新呈现。我该怎么做?我在这里发布代码:
**
LandingPage code:
**
import React, { useEffect, useState, useRef } from 'react'
import { Typography, Row } from 'antd';
import { API_URL, API_KEY, IMAGE_BASE_URL, IMAGE_SIZE, POSTER_SIZE } from '../../Config'
import MainImage from './Sections/MainImage'
import GridCard from '../../commons/GridCards'
import SearchMenu from '../LandingPage/Sections/SearchMenu'
const { Title } = Typography;
function LandingPage(props) {
const [searchTerm, setSearchTerm] = useState('');
console.log("searchIitialTerm = " + searchTerm);
const buttonRef = useRef(null);
const [Movies, setMovies] = useState([])
const [MainMovieImage, setMainMovieImage] = useState(null)
const [Loading, setLoading] = useState(true)
const [CurrentPage, setCurrentPage] = useState(0)
console.log(props.SearchMenu);
console.log("searchTermLanding = " + searchTerm);
var path;
var loadpath;
onchange = (searchTerm) => {
if (searchTerm != '') {
path = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=1`;
loadpath = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=${CurrentPage + 1}`;
}
else if (searchTerm == '') {
path = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
loadpath = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${CurrentPage + 1}`;
}
}
useEffect(() => {
const endpoint = path;
fetchMovies(endpoint)
}, [])
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, [])
const fetchMovies = (endpoint) => {
fetch(endpoint)
.then(result => result.json())
.then(result => {
// console.log(result)
// console.log('Movies',...Movies)
// console.log('result',...result.results)
setMovies([...Movies, ...result.results])
setMainMovieImage(MainMovieImage || result.results[0])
setCurrentPage(result.page)
}, setLoading(false))
.catch(error => console.error('Error:', error)
)
}
const loadMoreItems = () => {
let endpoint = '';
setLoading(true)
console.log('CurrentPage', CurrentPage)
endpoint = loadpath;
fetchMovies(endpoint);
}
const handleScroll = () => {
const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight - 1) {
// loadMoreItems()
console.log('clicked')
buttonRef.current.click();
}
}
return (
<div>
<div className="menu__container menu_search">
<SearchMenu mode="horizontal" onChange={value => setSearchTerm(value)} />
</div>
<div style={{ width: '100%', margin: '0' }}>
{MainMovieImage &&
<MainImage
image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${MainMovieImage.backdrop_path}`}
title={MainMovieImage.original_title}
text={MainMovieImage.overview}
/>
}
<div style={{ width: '85%', margin: '1rem auto' }}>
<Title level={2} > Latest movies </Title>
<hr />
<Row gutter={[16, 16]}>
{Movies && Movies.map((movie, index) => (
<React.Fragment key={index}>
<GridCard
image={movie.poster_path ?
`${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
: null}
movieId={movie.id}
movieName={movie.original_title}
/>
</React.Fragment>
))}
</Row>
{Loading &&
<div>Loading...</div>}
<br />
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button ref={buttonRef} className="loadMore" onClick={loadMoreItems}>Load More</button>
</div>
</div>
</div>
</div>
)
}
export default LandingPage
**
SearchMenu code:
**
import React, { useState } from 'react';
import { Route, Switch } from "react-router-dom";
import { Menu } from 'antd';
import { Input } from 'antd';
//import LandingPage from '../../LandingPage/LandingPage';
import '../../NavBar/Sections/Navbar.css';
const SearchMenu = (props) => {
console.log("props = " + props);
const [searchTerm, setSearchTerm] = useState("");
const { Search } = Input;
const onSearch = value => console.log(value);
function searchChangeHandler(e) {
e.preventDefault();
console.log(e.target.value);
setSearchTerm(e.target.value);
props.onChange(e.target.value);
}
console.log("searchTerm = " + searchTerm);
//console.log(props.onChange);
return (
<div className="searchMenu">
<Menu mode={props.mode} />
<Search
placeholder="Search"
allowClear onSearch={onSearch}
style={{ width: 400 }}
onChange={(e) => searchChangeHandler(e)}
/>
{/*
console.log("Search Term = " + searchTerm);
<LandingPage search={searchTerm}/>
*/}
</div>
)
}
export default SearchMenu;
LandingPage
中的 searchTerm
状态发生变化,但这不会触发对 API 数据的任何更新。您为搜索词定义了一个 onchange
函数,但您没有在任何地方调用它。
您可以在每次击键时重新进行搜索,或者您可以响应搜索按钮的点击和搜索输入中的 onPressEnter
。我将在每次更改时重新搜索。所以我们已经 searchTerm
更新了——我们只需要使用它!
我认为在加载数据之前而不是之后设置currentPage
是有意义的,但这只是我的意见。这样效果就可以响应页面和查询的变化。
试试这个:
function LandingPage() {
const [searchTerm, setSearchTerm] = useState("");
const [movies, setMovies] = useState([]);
const [mainMovieImage, setMainMovieImage] = useState(null);
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
// do you need this? could just call loadMoreItems() instead of click()
const buttonRef = useRef(null);
const loadMoreItems = () => {
// just set the page, the effect will respond to it
setCurrentPage((page) => page + 1);
};
const onChangeSearch = (value) => {
// reset page to 1 when changing search
setSearchTerm(value);
setCurrentPage(1);
};
// run effect to load movies when the page or the searchTerm changes
useEffect(() => {
const endpoint =
searchTerm === ""
? `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${currentPage}`
: `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${encodeURIComponent(
searchTerm
)}&page=${currentPage}`;
// could use async/await but promise/then is fine too
setLoading(true);
fetch(endpoint)
.then((response) => response.json())
.then((json) => {
// replace state on page 1 of a new search
// otherwise append to exisiting
setMovies((previous) =>
currentPage === 1 ? json.results : [...previous, ...json.results]
);
// only replace if not already set
// when should we reset this?
setMainMovieImage((previous) => previous || json.results[0]);
})
.catch((error) => console.error("Error:", error))
.finally(() => setLoading(false));
}, [searchTerm, currentPage]);
const handleScroll = () => {
const windowHeight =
"innerHeight" in window
? window.innerHeight
: document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight - 1) {
// loadMoreItems()
console.log("clicked");
buttonRef.current?.click();
}
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
// cleanup function
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div>
<div className="menu__container menu_search">
<SearchMenu
mode="horizontal"
value={searchTerm}
onChange={onChangeSearch}
/>
</div>
<div style={{ width: "100%", margin: "0" }}>
{mainMovieImage && (
<MainImage
image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${mainMovieImage.backdrop_path}`}
title={mainMovieImage.original_title}
text={mainMovieImage.overview}
/>
)}
<div style={{ width: "85%", margin: "1rem auto" }}>
<Title level={2}> Latest movies </Title>
<hr />
<Row gutter={[16, 16]}>
{movies &&
movies.map((movie, index) => (
<React.Fragment key={index}>
<GridCard
image={
movie.poster_path
? `${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
: null
}
movieId={movie.id}
movieName={movie.original_title}
/>
</React.Fragment>
))}
</Row>
{loading && <div>Loading...</div>}
<br />
<div style={{ display: "flex", justifyContent: "center" }}>
<button
ref={buttonRef}
className="loadMore"
onClick={loadMoreItems}
disabled={loading} // disable button when fetching results
>
Load More
</button>
</div>
</div>
</div>
</div>
);
}
我会把 SearchMenu
变成一个受控组件,它可以从 LandingPage
读取和更新 searchTerm
状态,而不是拥有自己的状态。
const SearchMenu = ({ mode, value, onChange }) => {
return (
<div className="searchMenu">
<Menu mode={mode} />
<Search
value={value}
placeholder="Search"
allowClear
style={{ width: 400 }}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
};
我正在使用两个组件:LandingPage 和 SearchMovie。 SearchMovie 组件正在更新 searchTerm (onChange) 并将其传递给从 API 获取电影的 LandingPage(父组件)。我签入 console.log 并且 SearchTerm 状态正在更新,但 LandingPage 未使用更新的 searchTerm 状态重新呈现。我该怎么做?我在这里发布代码:
**
LandingPage code:
**
import React, { useEffect, useState, useRef } from 'react'
import { Typography, Row } from 'antd';
import { API_URL, API_KEY, IMAGE_BASE_URL, IMAGE_SIZE, POSTER_SIZE } from '../../Config'
import MainImage from './Sections/MainImage'
import GridCard from '../../commons/GridCards'
import SearchMenu from '../LandingPage/Sections/SearchMenu'
const { Title } = Typography;
function LandingPage(props) {
const [searchTerm, setSearchTerm] = useState('');
console.log("searchIitialTerm = " + searchTerm);
const buttonRef = useRef(null);
const [Movies, setMovies] = useState([])
const [MainMovieImage, setMainMovieImage] = useState(null)
const [Loading, setLoading] = useState(true)
const [CurrentPage, setCurrentPage] = useState(0)
console.log(props.SearchMenu);
console.log("searchTermLanding = " + searchTerm);
var path;
var loadpath;
onchange = (searchTerm) => {
if (searchTerm != '') {
path = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=1`;
loadpath = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=${CurrentPage + 1}`;
}
else if (searchTerm == '') {
path = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
loadpath = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${CurrentPage + 1}`;
}
}
useEffect(() => {
const endpoint = path;
fetchMovies(endpoint)
}, [])
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, [])
const fetchMovies = (endpoint) => {
fetch(endpoint)
.then(result => result.json())
.then(result => {
// console.log(result)
// console.log('Movies',...Movies)
// console.log('result',...result.results)
setMovies([...Movies, ...result.results])
setMainMovieImage(MainMovieImage || result.results[0])
setCurrentPage(result.page)
}, setLoading(false))
.catch(error => console.error('Error:', error)
)
}
const loadMoreItems = () => {
let endpoint = '';
setLoading(true)
console.log('CurrentPage', CurrentPage)
endpoint = loadpath;
fetchMovies(endpoint);
}
const handleScroll = () => {
const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight - 1) {
// loadMoreItems()
console.log('clicked')
buttonRef.current.click();
}
}
return (
<div>
<div className="menu__container menu_search">
<SearchMenu mode="horizontal" onChange={value => setSearchTerm(value)} />
</div>
<div style={{ width: '100%', margin: '0' }}>
{MainMovieImage &&
<MainImage
image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${MainMovieImage.backdrop_path}`}
title={MainMovieImage.original_title}
text={MainMovieImage.overview}
/>
}
<div style={{ width: '85%', margin: '1rem auto' }}>
<Title level={2} > Latest movies </Title>
<hr />
<Row gutter={[16, 16]}>
{Movies && Movies.map((movie, index) => (
<React.Fragment key={index}>
<GridCard
image={movie.poster_path ?
`${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
: null}
movieId={movie.id}
movieName={movie.original_title}
/>
</React.Fragment>
))}
</Row>
{Loading &&
<div>Loading...</div>}
<br />
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button ref={buttonRef} className="loadMore" onClick={loadMoreItems}>Load More</button>
</div>
</div>
</div>
</div>
)
}
export default LandingPage
**
SearchMenu code:
**
import React, { useState } from 'react';
import { Route, Switch } from "react-router-dom";
import { Menu } from 'antd';
import { Input } from 'antd';
//import LandingPage from '../../LandingPage/LandingPage';
import '../../NavBar/Sections/Navbar.css';
const SearchMenu = (props) => {
console.log("props = " + props);
const [searchTerm, setSearchTerm] = useState("");
const { Search } = Input;
const onSearch = value => console.log(value);
function searchChangeHandler(e) {
e.preventDefault();
console.log(e.target.value);
setSearchTerm(e.target.value);
props.onChange(e.target.value);
}
console.log("searchTerm = " + searchTerm);
//console.log(props.onChange);
return (
<div className="searchMenu">
<Menu mode={props.mode} />
<Search
placeholder="Search"
allowClear onSearch={onSearch}
style={{ width: 400 }}
onChange={(e) => searchChangeHandler(e)}
/>
{/*
console.log("Search Term = " + searchTerm);
<LandingPage search={searchTerm}/>
*/}
</div>
)
}
export default SearchMenu;
LandingPage
中的 searchTerm
状态发生变化,但这不会触发对 API 数据的任何更新。您为搜索词定义了一个 onchange
函数,但您没有在任何地方调用它。
您可以在每次击键时重新进行搜索,或者您可以响应搜索按钮的点击和搜索输入中的 onPressEnter
。我将在每次更改时重新搜索。所以我们已经 searchTerm
更新了——我们只需要使用它!
我认为在加载数据之前而不是之后设置currentPage
是有意义的,但这只是我的意见。这样效果就可以响应页面和查询的变化。
试试这个:
function LandingPage() {
const [searchTerm, setSearchTerm] = useState("");
const [movies, setMovies] = useState([]);
const [mainMovieImage, setMainMovieImage] = useState(null);
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
// do you need this? could just call loadMoreItems() instead of click()
const buttonRef = useRef(null);
const loadMoreItems = () => {
// just set the page, the effect will respond to it
setCurrentPage((page) => page + 1);
};
const onChangeSearch = (value) => {
// reset page to 1 when changing search
setSearchTerm(value);
setCurrentPage(1);
};
// run effect to load movies when the page or the searchTerm changes
useEffect(() => {
const endpoint =
searchTerm === ""
? `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${currentPage}`
: `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${encodeURIComponent(
searchTerm
)}&page=${currentPage}`;
// could use async/await but promise/then is fine too
setLoading(true);
fetch(endpoint)
.then((response) => response.json())
.then((json) => {
// replace state on page 1 of a new search
// otherwise append to exisiting
setMovies((previous) =>
currentPage === 1 ? json.results : [...previous, ...json.results]
);
// only replace if not already set
// when should we reset this?
setMainMovieImage((previous) => previous || json.results[0]);
})
.catch((error) => console.error("Error:", error))
.finally(() => setLoading(false));
}, [searchTerm, currentPage]);
const handleScroll = () => {
const windowHeight =
"innerHeight" in window
? window.innerHeight
: document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight - 1) {
// loadMoreItems()
console.log("clicked");
buttonRef.current?.click();
}
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
// cleanup function
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div>
<div className="menu__container menu_search">
<SearchMenu
mode="horizontal"
value={searchTerm}
onChange={onChangeSearch}
/>
</div>
<div style={{ width: "100%", margin: "0" }}>
{mainMovieImage && (
<MainImage
image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${mainMovieImage.backdrop_path}`}
title={mainMovieImage.original_title}
text={mainMovieImage.overview}
/>
)}
<div style={{ width: "85%", margin: "1rem auto" }}>
<Title level={2}> Latest movies </Title>
<hr />
<Row gutter={[16, 16]}>
{movies &&
movies.map((movie, index) => (
<React.Fragment key={index}>
<GridCard
image={
movie.poster_path
? `${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
: null
}
movieId={movie.id}
movieName={movie.original_title}
/>
</React.Fragment>
))}
</Row>
{loading && <div>Loading...</div>}
<br />
<div style={{ display: "flex", justifyContent: "center" }}>
<button
ref={buttonRef}
className="loadMore"
onClick={loadMoreItems}
disabled={loading} // disable button when fetching results
>
Load More
</button>
</div>
</div>
</div>
</div>
);
}
我会把 SearchMenu
变成一个受控组件,它可以从 LandingPage
读取和更新 searchTerm
状态,而不是拥有自己的状态。
const SearchMenu = ({ mode, value, onChange }) => {
return (
<div className="searchMenu">
<Menu mode={mode} />
<Search
value={value}
placeholder="Search"
allowClear
style={{ width: 400 }}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
};