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;
所以這是父元件,我從這裡將 handleNewLikes 函數作為 prop 傳遞給 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;
這是行組件,在其中我將handleNewLikes 函數傳遞給媒體。
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;
在媒體中,該函數有時會在控制台中定義,有時會未定義。另外,當我單擊心形圖示並觸發handleLikes函數時,其中的handleNewLikes函數將始終顯示為未定義。
這就是我在控制台中看到的
謝謝你的幫助,我被困了幾天了lmao
我知道我可能可以使用某種狀態管理庫,但這需要相當多的重構,我覺得這應該可行,我不明白為什麼會出現任何問題,除非我目光短淺。
P粉7627302052024-02-27 09:48:52
我複製了你的程式碼,並添加了一些日誌來幫助你看到發生了什麼。你只從Home -> Main
和Home -> Row 0
傳遞了handleNewLikes
。
其餘的行的handleNewLikes
正確地為undefined
,因為你沒有傳遞這個函數。查看這些日誌以了解我的意思:
當我們到達Media
元件時,你在呼叫handleLikes
時嘗試使用一個undefined
的handleNewLikes
函數。
// 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!"); } };
這是在Media
元件中點擊like按鈕
時的一些日誌:
最簡單的解決方案是在Home
中更新Row
實現的參數,包括handleNewLikes
:
<Row rowId='1' title='Top 10 Movies in the U.S. Today' fetchURL={requests.popular} handleNewLikes={handleNewLikes} /> // 同樣適用於row 2、3等...
這是在將函數傳遞給Row 1
後點擊like按鈕的日誌
^ 你應該使用某種狀態管理。你不需要使用一個函式庫來做這個。如果狀態不是非常複雜,你可以使用類似於你在AuthContext
中所做的React上下文。