I am making a movie review app. I have a Home component which renders card components and passes them the "movie" prop. The API call is inside of the Home component. Inside the card component I have a button which, on click, should send the movie prop to a different component called Watchlist. I get the error "handleAddToWatchlist is not a function". it is provided by the watchlist component and executed on click of the button. Please help.
Card component:
const Card = ({movie, handleAddToWatchList}) => {
const { Title, Year, Actors, Genre, Image } = movie;
const movieInfo = (event) => {
event.preventDefault();
console.log(movie);
}
return(
<div className="card">
<div className="card-image">
<img src={Image} alt="interstellar"/>
</div>
<div className="card-info">
<h2 className="movie-title">{Title}</h2>
<p className="movie-year">Year: {Year}</p>
<p className="movie-genre">Genre: {Genre}</p>
<p className="movie-actor">Actor: {Actors}</p>
</div>
<div className="movie-buttons">
<button ><span class="material-symbols-outlined">star</span></button>
<button onClick={() => handleAddToWatchList(movie)}><span className="material-symbols-outlined">add</span></button>
</div>
</div>
)
}
export default Card;
Watchlist component:
const Watchlist = () => {
const { watchlistData, setWatchlist } = useAppContext();
const handleAddToWatchList = (movie) => {
if (!watchlistData.some((item) => item.id === movie.imdbID)) {
setWatchlist((prevData) => [...prevData, movie]);
} else {
console.log(`${movie.title} is already in the watchlist.`);
}
}
return (
<>
<nav>
<ul>
<li> <Link to="/" style={linkStyle}> Log Out </Link> </li>
<li> <Link to="/reviews" style={linkStyle}>My Reviews</Link> </li>
<li> <Link to="/home" style={linkStyle}> Home </Link></li>
</ul>
</nav>
<div className="main-container">
<div className="main">
<div className="movie-container-with-filter">
<div className="movie-container">
{watchlistData && watchlistData.length > 0 ? (
watchlistData.map((movie) => (
<Card
key={movie.imdbID}
movie={movie}
handleAddToWatchList={handleAddToWatchList}
/>
))
) : (
<p>No movies in the watchlist</p>
)
}
</div>
</div>
</div>
</div
)
Home.js
const Home = () => {
const [currentPage, setCurrentPage] = useState(1)
const [movies, setMovies] = useState([])
const [recordsPerPage] = useState(9)
const [ searchfield, setSearchfield ] = useState('')
const [dateFilter, setDateFilter] = useState(0);
const [ selectedGenre, setSelectedGenre ] = useState("");
const uniqueMovieIds = useRef(new Set());
const apiKey = "apikey"
const indexOfLastRecord = currentPage * recordsPerPage;
const indexOfFirstRecord = indexOfLastRecord - recordsPerPage
const movieContainerRef = useRef(null)
/*const fetchMovies = async () => {
try {
const response = await fetch(`http://www.omdbapi.com/?s=action&page=${currentPage}&apikey=${apiKey}&plot=full`)
const data = await response.json()
console.log(data);
if (data.Response === 'True' && Array.isArray(data.Search)) {
// Filter out duplicates by IMDb ID
const uniqueMovies = data.Search.filter(newMovie => !uniqueMovieIds.current.has(newMovie.imdbID));
// Update the Set with new IMDb IDs
uniqueMovies.forEach(newMovie => uniqueMovieIds.current.add(newMovie.imdbID));
// SetMovies with the combination of existing movies and unique new movies
setMovies(prevMovies => [...prevMovies, ...uniqueMovies]);
}
else {
console.error("Error fetching movies: ", data)
}
}catch (error) {
console.error("Error fetching data: ", error);
}
}*/
const fetchMovies = async () => {
try {
const response = await fetch(`http://www.omdbapi.com/?s=action&page=${currentPage}&apikey=${apiKey}`);
const data = await response.json();
if (data.Response === 'True' && Array.isArray(data.Search)) {
const detailedMovies = await Promise.all(
data.Search.map(async (newMovie) => {
const detailsResponse = await fetch(`http://www.omdbapi.com/?i=${newMovie.imdbID}&apikey=${apiKey}`);
const detailsData = await detailsResponse.json();
console.log(detailsData);
return detailsData;
})
);
setMovies(prevMovies => [...prevMovies, ...detailedMovies]);
} else {
console.error("Error fetching movies: ", data);
}
} catch (error) {
console.error("Error fetching data: ", error);
}
};
//FILTERING
const handleGenreFilter = (event) => {
const selectedGenre = event.target.value;
setSelectedGenre(selectedGenre)
}
const onSearchChange = (event) => {
setCurrentPage(1);
uniqueMovieIds.current.clear();
setSearchfield(event.target.value);
}
const handleYearFilter = (event) => {
const dateFilter = event.target.value;
setDateFilter(dateFilter);
}
const applyFilters = () => {
if(searchfield === "" || dateFilter === 0 || selectedGenre === "") {
return movies
}
console.log("Year:", dateFilter);
console.log("Genre:", selectedGenre);
console.log("Search:", searchfield);
let filteredMovies = movies;
console.log("Movies: ", filteredMovies);
if (searchfield !== "") {
filteredMovies = filteredMovies.filter(movie => movie.Title.toLowerCase().includes(searchfield.toLowerCase()));
}
console.log("Fiultered by search: ",filteredMovies);
if (dateFilter !== 0) {
filteredMovies = filteredMovies.filter(movie => movie.Year == dateFilter);
}
console.log("Fiultered by date: ",filteredMovies);
if (selectedGenre !== '') {
const selectedGenresArray = selectedGenre.split(",").map(genre => genre.trim())
filteredMovies = filteredMovies.filter(movie => {
const movieGenresArray = movie.Genre.split(",").map(genre => genre.trim())
return selectedGenresArray.some(selectedGenre => movieGenresArray.includes(selectedGenre))
});
}
setMovies(filteredMovies);
console.log("FilteredMovies",filteredMovies);
}
const handleFilterButtonClick = () => {
applyFilters();
}
const handleResetButtonClick = () => {
setSearchfield("");
setDateFilter(0)
setSelectedGenre("")
applyFilters();
}
const loadMore = () => {
setCurrentPage(prevPage => prevPage + 1);
if (movieContainerRef.current) {
const containerTop = movieContainerRef.current.offsetTop;
if(window.innerWidth <= 600) {
document.getElementById("mainContainer").scrollIntoView({
behavior:"smooth",
});
} else {
movieContainerRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
}
}
useEffect(() => {
fetchMovies()
}, [currentPage])
const handleAddToWatchList = () => {
console.log("works");
}
return (
<>
<nav>
<ul>
<li className="nav-links-text"> <Link to="/" style={linkStyle}> Log Out </Link> </li>
<li className="nav-links-text"> <Link to="/watchlist" style={linkStyle}>Watchlist </Link> </li>
{/*kad napravis usere, postavi path da bude "/:uid/watchlist"*/}
<li className="nav-links-text"> <Link to="/reviews" style={linkStyle}> My Reviews </Link></li>
<li className="title-home">mrabyEMIR</li>
</ul>
</nav>
<div id="mainContainer" className="main-container">
<div className="main">
<div className="movie-container-with-filter">
<div ref={movieContainerRef} className="movie-container">
<div className="movies">
{
movies.slice(indexOfFirstRecord, indexOfLastRecord).map((movie) => {
return <Card key={movie.imdbID}
movie={{
Image:movie.Poster,
Title:movie.Title,
Year:movie.Year,
Actors:movie.Actors,
Genre:movie.Genre
}}
/>
})
}
</div>
</div>
<div className="movie-filter">
<label>Search</label>
<input onChange={onSearchChange} type="search" style={{color:"black"}}/>
<label>Year:</label>
<input type="number" style={{color:"black"}} onChange={handleYearFilter}/>
<label>Genre</label>
<select style={{color:"black"}} onChange={handleGenreFilter}>
<option value="">Select Genre</option>
<option value="Adventure">Adventure</option>
<option value="Western">Western</option>
<option value="Musical">Musical</option>
<option value="Thriller">Thriller</option>
<option value="Sci-Fi">Sci-Fi</option>
<option value="Action">Action</option>
<option value="Horror">Horror</option>
</select>
<button onClick={handleFilterButtonClick}>FILTER</button>
<input type="reset" id="reset" value={"RESET"} style={{color:"black"}} onClick={handleResetButtonClick}/>
</div>
</div>
</div>
<div className="load-more-container">
<button className="load-more" onClick={loadMore}>Next Page</button>
</div>
</div>
</>
)
}
export default Home;
AppContext.js
const AppContext = createContext();
export function AppProvider({ children }) {
const [watchlistData, setWatchlistData] = useState([]);
return (
<AppContext.Provider value={{ watchlistData, setWatchlistData }}>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<AppProvider>
<App/>
</AppProvider>
</React.StrictMode>
);
I have tried everything and resorted to useAppContext even though I am very unfamiliar with react context.
2
Answers
So I actually figured out the best way to go about this is to use React Context. I set up two main states: mainMovies and watchlist, which can be accessible from anywhere.
AppContext.js
Watchlist.js
Card.js
Thanks X8inez for contributing to the thought process.
In your
Card
component,handleAddToWatchList
is supposed to be passed to toonClick
as soonClick={handleAddToWatchList}
and inWatchlist
, this is how you call the functionhandleAddToWatchList={() => handleAddToWatchList(movie)}
The bug occurs because
handleAddToWatchList
is a required prop in theCard
component and it’s not used in theHome
component.You can pass in an empty function to make
handleAddToWatchList
optional since it isn’t used in theHome
component.In your
Home
component, addhandleAddToWatchList={() => {}}
to yourCard
component as a prop