import React, { useContext, useState } from 'react'; import Main from '../components/Main'; import Row from '../components/Row'; import requests from '../requests'; import AppContext from '../lib/AuthContext'; const Home = () => { const [likedItems, setLikedItems] = useState([]); const contextValue = useContext(AppContext); const token = window.localStorage.getItem('trailerflix-jwt'); const handleNewLikes = item => { setLikedItems(prevLikedItems => [...prevLikedItems, item]); }; if (token && contextValue.user?.user) { return ( <> <Main handleNewLikes={handleNewLikes} /> <Row rowId='0' title={`${(token && contextValue.user?.user) ? contextValue?.user?.user.username : null}'s List`} fetchURL='/auth/get-likes' likedItems={likedItems} handleNewLikes={handleNewLikes} /> <Row rowId='1' title='Top 10 Movies in the U.S. Today' fetchURL={requests.popular} /> <Row rowId='2' title='Coming Soon' fetchURL={requests.upcoming} /> <Row rowId='3' title='Trending Now' fetchURL={requests.trending} /> <Row rowId='4' title='Now Playing in Theaters' fetchURL={requests.nowPlaying} /> <Row rowId='5' title='Animation' fetchURL={requests.animation} /> <Row rowId='6' title='Horror' fetchURL={requests.horror} /> <Row rowId='7' title='Comedy' fetchURL={requests.comedy} /> </> ); } else { return ( <> <Main /> <Row rowId='1' title='Top 10 Movies in the U.S. Today' fetchURL={requests.popular} /> <Row rowId='2' title='Coming Soon' fetchURL={requests.upcoming} /> <Row rowId='3' title='Trending Now' fetchURL={requests.trending} /> <Row rowId='4' title='Now Playing in Theaters' fetchURL={requests.nowPlaying} /> <Row rowId='5' title='Animation' fetchURL={requests.animation} /> <Row rowId='6' title='Horror' fetchURL={requests.horror} /> <Row rowId='7' title='Comedy' fetchURL={requests.comedy} /> </> ); } }; export default Home;
So this is the parent component and from here I pass the handleNewLikes function as prop to Row.
import axios from 'axios'; import React, { useEffect, useState, useRef } from 'react'; import Media from './Media'; import { MdChevronLeft, MdChevronRight } from 'react-icons/md'; const Row = ({ title, fetchURL, rowId, videos, likedItems, handleNewLikes }) => { const [media, setMedia] = useState([]); useEffect(() => { const token = window.localStorage.getItem('trailerflix-jwt'); if (token && fetchURL === '/auth/get-likes') { axios.get(fetchURL, { headers: { 'Content-Type': 'application/json', 'X-Access-Token': `${token}` } }) .then(res => { const flattenedArray = res.data.map(item => item.favoritedItem); const newestLikesFirst = flattenedArray.reverse(); setMedia(newestLikesFirst); }) .catch(error => { console.error('Axios GET request failed:', error); }); } else if (fetchURL !== '/auth/get-likes') { axios.get(fetchURL) .then(response => { setMedia(response.data.results); }) .catch(error => { console.error('Axios GET request failed:', error); }); } }, [fetchURL, likedItems]); const validMedia = []; for (let i = 0; i < media.length; i++) { if (media[i].backdrop_path !== null) { validMedia.push(media[i]); } else { media.splice(i, 1); } } const rowRef = useRef(null); const [isMoved, setIsMoved] = useState(false); const handleSlider = direction => { setIsMoved(true); if (rowRef.current) { const { scrollLeft, clientWidth } = rowRef.current; const scrollTo = direction === 'left' ? scrollLeft - clientWidth : scrollLeft + clientWidth; rowRef.current.scrollTo({ left: scrollTo, behavior: 'smooth' }); } }; return ( <> <h2 className='text-white font-bold md:text-2xl p-4 mt-8 mb-3 ml-4'>{title}</h2> <div className='relative flex items-center group mb-10 ml-4'> <MdChevronLeft className={`text-white bg-transparent left-0 absolute hover:opacity-100 cursor-pointer z-10 hidden invisible lg:visible md:visible group-hover:block ${!isMoved && ' lg:invisible md:invisible'}`} size={60} onClick={() => handleSlider('left')} /> <div id={'slider' + rowId} className='w-full h-full overflow-x-scroll whitespace-nowrap scroll-smooth relative scrollbar-hide overflow-y-hidden' ref={rowRef}> {validMedia.map((item, id) => { return <Media key={id} item={item} rowId={rowId} handleNewLikes={handleNewLikes} likedItems={likedItems}/>; })} </div> <MdChevronRight className='text-white bg-transparent right-0 absolute hover:opacity-100 cursor-pointer z-10 hidden invisible lg:visible md:visible group-hover:block' size={60} onClick={() => handleSlider('right')} /> </div> </> ); }; export default Row;
This is the row component where I pass the handleNewLikes function to the media.
import React, { useState, useContext, useEffect } from 'react'; import axios from 'axios'; import { FaHeart, FaRegHeart } from 'react-icons/fa'; import Player from './Player'; import AppContext from '../lib/AuthContext'; const Media = ({ item, rowId, handleNewLikes, likedItems }) => { const contextValue = useContext(AppContext); const [watchClicked, setWatchClicked] = useState(false); const [isLiked, setIsLiked] = useState(false); const [key, setKey] = useState(''); const [playTrailer, setPlayTrailer] = useState(false); const [noTrailer, setNoTrailer] = useState(false); useEffect(() => { handleFavoritesList(); }, []); console.log(handleNewLikes); const handleTrailerClick = () => { axios .get(`https://api.themoviedb.org/3/movie/${item.id}?api_key=${process.env.MOVIEDB_API_KEY}&append_to_response=videos`) .then(response => { const trailer = response.data.videos.results.find(vid => vid.name === 'Official Trailer'); if (response.data.videos.results.length === 0) { setKey(''); setPlayTrailer(false); setNoTrailer(true); document.body.style.overflowY = 'hidden'; } else if (trailer) { setKey(trailer); setPlayTrailer(true); } else if (!trailer) { setKey(response.data.videos.results[0]); setPlayTrailer(true); } }); setWatchClicked(true); document.body.style.overflowY = 'hidden'; }; const handleLikes = () => { const token = window.localStorage.getItem('trailerflix-jwt'); if (token && contextValue?.user?.user) { axios.post('/auth/likes', item, { headers: { 'Content-Type': 'application/json', 'X-Access-Token': token } }) .then(response => { console.log(handleNewLikes); handleNewLikes(item); setIsLiked(true); }) .catch(error => { console.error('Fetch failed during POST', error); }); } else { window.alert('You need to be signed in to save a movie!'); } }; const handleFavoritesList = () => { const token = window.localStorage.getItem('trailerflix-jwt'); if (token && contextValue.user?.user) { axios.get('/auth/get-likes', { headers: { 'Content-Type': 'application/json', 'X-Access-Token': token } }) .then(response => { const result = response.data; const isItemLiked = result.some(obj => obj.favoritedItem.id === item.id); setIsLiked(isItemLiked); }) .catch(error => { console.error('Fetch failed during GET', error); }); } else { setIsLiked(false); } }; const truncateString = (str, num) => { if (str?.length > num) { return str.slice(0, num) + '...'; } else { return str; } }; const { title, original_title, media_type, name } = item; return ( <> {watchClicked && ( <Player trailer={key} playTrailer={playTrailer} noTrailer={noTrailer} onClose={() => setWatchClicked(false)} /> )} <div className="w-[200px] sm:w-[300px] lg:w-[400px] inline-block cursor-pointer relative transition duration-200 ease-out p-2 lg:mr-1 sm:mr-2 md:hover:scale-105"> <img className="w-full h-auto block rounded-sm object-cover md:rounded" src={`https://image.tmdb.org/t/p/w500/${rowId === '1' || rowId === '4' ? item?.poster_path : item?.backdrop_path}`} alt={title || original_title || name || media_type || 'Title Unavailable'} /> <div className="absolute top-0 left-0 w-full h-full hover:bg-black/80 opacity-0 hover:opacity-100 ease-in duration-300 text-white"> <div className="white-space-normal text-xs md:text-sm lg:text-base font-bold flex justify-center items-center text-center h-full"> <div className="flex-wrap"> <p className="mb-2"> {truncateString(title || original_title || name || media_type || 'Title Unavailable', 35)} </p> <div> <a className="border bg-gray-300 text-black border-gray-300 py-1 px-1 text-xs lg:text-base hover:bg-red-600 hover:border-red-600 hover:text-gray-300 ease-in duration-250" onClick={handleTrailerClick} > Watch </a> </div> </div> </div> <p onClick={() => handleLikes()}> {isLiked && contextValue.user?.user ? ( <FaHeart className="absolute top-4 left-4 text-red-600" /> ) : ( <FaRegHeart className="absolute top-4 left-4 hover:text-red-600 ease-in duration-100" /> )} </p> </div> </div> </> ); }; export default Media;
In media, the function is sometimes defined in the console and sometimes undefined. Also, when I click on the heart icon and the handleLikes function is triggered, the handleNewLikes function within it will always appear as undefined.
This is what I see in the console
Thank you for your help, I've been stuck for a few days lmao
I know I could probably use some kind of state management library, but that would require quite a bit of refactoring, and I feel like this should work, and I don't see why there would be any problems, unless I'm shortsighted.
P粉7627302052024-02-27 09:48:52
I copied your code and added some logs to help you see what's going on. You only passed handleNewLikes
from Home -> Main
and Home -> Row 0
.
The rest of the rows' handleNewLikes
is correctly undefined
because you didn't pass this function. Check out these logs to see what I mean:
When we get to the Media
component, you try to use an handleNewLikes
function of undefined
when calling handleLikes
.
// Media.tsx line 48 const handleLikes = () => { const token = window.localStorage.getItem("trailerflix-jwt"); if (token && contextValue?.user?.user) { axios .post("/auth/likes", item, { headers: { "Content-Type": "application/json", "X-Access-Token": token } }) .then((response) => { console.log(handleNewLikes); // Undefined because not passed in to Row which passes to Media handleNewLikes(item); setIsLiked(true); }) .catch((error) => { console.error("Fetch failed during POST", error); }); } else { window.alert("You need to be signed in to save a movie!"); } };
This is some logs when clicking the like button
in the Media
component:
The simplest solution is to update the parameters of the Row
implementation in Home
, including handleNewLikes
:
<Row rowId='1' title='Top 10 Movies in the U.S. Today' fetchURL={requests.popular} handleNewLikes={handleNewLikes} /> //Same applies to row 2, 3, etc...
This is the log of clicking the like button after passing the function to Row 1
^ You should use some kind of state management. You don't need to use a library to do this. If the state is not very complex, you can use a React context similar to what you did in AuthContext
.