I am using two components: LandingPage and SearchMovie. SearchMovie component is updating searchTerm (onChange) and passing it to LandingPage(Parent component) which is fetching movies from API. I checked in console.log and SearchTerm state is updating but LandingPage is not re rendering with updated state of searchTerm. How can I do it? I am posting the code here:
**
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;
The searchTerm
state in LandingPage
changes, but this doesn't trigger any updates to the API data. You defined an onchange
function for the search term, but you haven't called it anywhere.
You can redo the search on every keystroke, or you can respond to clicks of a search button and onPressEnter
in your search input. I'm going to redo the search on every change. So we've already got searchTerm
updating -- we just need to use it!
I think it makes sense to set currentPage
before loading data rather than after, but that's just my opinion. That way the effect can respond to changes in the page and the query.
Try this:
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>
);
}
I would make SearchMenu
into a controlled component that both reads and updates the searchTerm
state from LandingPage
instead of having its own state.
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>
);
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.