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函数传递给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;
在媒体中,该函数有时会在控制台中定义,有时则未定义。另外,当我单击心形图标并触发handleLikes函数时,其中的handleNewLikes函数将始终显示为未定义。
感谢您的帮助,我被困了几天了 lmao
我知道我可能可以使用某种状态管理库,但这需要相当多的重构,我觉得这应该可行,我不明白为什么会出现任何问题,除非我目光短浅。
我复制了您的代码并添加了更多日志以帮助您了解发生了什么。您只是从
handleNewLikes
和 Home -> Main
传递了 Home -> Row 0
。
该行的其余部分正确地有
undefined
代表 handleNewLikes
,因为您没有向下传递该函数。查看这些日志以了解我的意思:
当我们到达
Media
组件时,您在调用 undefined
时尝试使用 handleNewLikes
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!");
}
};
以下是单击媒体组件中的
like buttons
时的一些日志:
最简单的解决方案是更新
Row
中 Home
实现的参数以包含 handleNewLikes
:
<Row
rowId='1'
title='Top 10 Movies in the U.S. Today'
fetchURL={requests.popular}
handleNewLikes={handleNewLikes}
/>
// same for row 2, 3, etc...
这是将函数传递到
Row 1
后单击“赞”按钮的日志
我知道我可能可以使用某种状态管理库,但是 这需要相当多的重构
^ 您应该使用某种状态管理。您不需要需要使用库来实现它。如果状态不是非常复杂,您可以使用 React 上下文,类似于您使用
AuthContext
所做的事情。