React项目报错:addToMovieList is not a function,请求排查原因
问题分析与解决方案
问题根源
报错Uncaught TypeError: addToMovieList is not a function的核心原因是:当在「收藏电影列表」标签页的Card组件中点击按钮时,该Card并未接收到addToMovieList函数。
你当前的MovieList组件会给它内部渲染的Card传递addToMovieList和removeFromMovieList,但展示收藏列表的Card(即source="saved"的场景),大概率是由另一个父组件(比如SavedMovies)渲染的,而这个父组件没有传递addToMovieList函数,导致调用时该参数为undefined。
而removeFromMovieList能正常工作,是因为它只在「原始电影列表」的Card中被调用,此时父组件MovieList已经正确传递了这个函数。
最优解决方案:状态提升
将movieList、savedMovies以及所有操作这两个列表的函数,统一放到它们的共同父组件(比如App.jsx)中管理,避免跨组件props传递缺失的问题,同时保证状态同步一致性。
1. 重构App组件(统一状态管理)
// App.jsx import { useState, useEffect } from 'react'; import MovieList from './components/MovieList'; import SavedMovies from './components/SavedMovies'; import axios from 'axios'; function App() { // 从localStorage初始化状态 const [movieList, setMovieList] = useState(() => { const stored = JSON.parse(localStorage.getItem('movieList')); return stored || []; }); const [savedMovies, setSavedMovies] = useState(() => { const stored = JSON.parse(localStorage.getItem('savedMovies')); return stored || []; }); // 首次加载时拉取电影列表 useEffect(() => { if (!movieList.length) { axios.get(`https://api.themoviedb.org/3/discover/movie?api_key=${import.meta.env.VITE_API_KEY}`) .then(res => setMovieList(res.data.results)) .catch(err => console.log(err)); } }, []); // 同步状态到localStorage useEffect(() => { localStorage.setItem('movieList', JSON.stringify(movieList)); }, [movieList]); useEffect(() => { localStorage.setItem('savedMovies', JSON.stringify(savedMovies)); }, [savedMovies]); // 从原始列表移到收藏列表 const addToSaved = (movieId) => { const targetMovie = movieList.find(m => m.id === movieId); if (targetMovie) { setSavedMovies(prev => [...prev, targetMovie]); setMovieList(prev => prev.filter(m => m.id !== movieId)); } }; // 从收藏列表移回原始列表 const removeFromSaved = (movieId) => { const targetMovie = savedMovies.find(m => m.id === movieId); if (targetMovie) { setMovieList(prev => [...prev, targetMovie]); setSavedMovies(prev => prev.filter(m => m.id !== movieId)); } }; return ( <div className="app"> {/* 假设是标签页切换,这里简化展示结构 */} <MovieList movieList={movieList} onSaveClick={addToSaved} /> <SavedMovies savedMovies={savedMovies} onRemoveClick={removeFromSaved} /> </div> ); } export default App;
2. 简化MovieList组件
// MovieList.jsx import "./movieList.scss"; import Card from "../card/Card"; function MovieList({ movieList, onSaveClick }) { return ( <div className="movieList"> <Card itemList={movieList} source="movie" onButtonClick={onSaveClick} /> </div> ); } export default MovieList;
3. 创建SavedMovies组件
// SavedMovies.jsx import "./savedMovies.scss"; import Card from "../card/Card"; function SavedMovies({ savedMovies, onRemoveClick }) { return ( <div className="savedMovies"> <Card itemList={savedMovies} source="saved" onButtonClick={onRemoveClick} /> </div> ); } export default SavedMovies;
4. 简化Card组件
// Card.jsx import { useState } from "react"; import "./card.scss"; function Card({ itemList, source, onButtonClick }) { const [activeId, setActiveId] = useState(null); const handleClick = (item) => { onButtonClick(item.id); setActiveId(prev => prev === item.id ? null : item.id); }; return ( <div className="cardContainer"> {itemList.map((item) => ( <div className="card" key={item.id}> <div className="save"> <button onClick={() => handleClick(item)} className={`button ${activeId === item.id ? "saved" : ""}`} > <img src={source === "saved" ? "/trash.png" : "/star.png"} alt={source === "saved" ? "移除收藏" : "添加收藏"} /> </button> </div> <img src={`https://image.tmdb.org/t/p/w500${item.poster_path}`} alt={item.title} /> <p>{item.title}</p> </div> ))} </div> ); } export default Card;
临时快速修复(不推荐长期使用)
如果暂时不想做状态提升,需要确保展示收藏列表的Card父组件也传入addToMovieList函数,同时手动同步localStorage和组件状态:
// 示例SavedMovies组件(临时方案) import { useState, useEffect } from 'react'; import Card from './Card'; function SavedMovies() { const [savedMovies, setSavedMovies] = useState(() => { return JSON.parse(localStorage.getItem('savedMovies')) || []; }); const addToMovieList = (movieId) => { const targetMovie = savedMovies.find(m => m.id === movieId); if (targetMovie) { // 从localStorage获取当前原始列表并更新 const currentMovieList = JSON.parse(localStorage.getItem('movieList')) || []; currentMovieList.push(targetMovie); localStorage.setItem('movieList', JSON.stringify(currentMovieList)); // 更新收藏列表 setSavedMovies(prev => prev.filter(m => m.id !== movieId)); localStorage.setItem('savedMovies', JSON.stringify(savedMovies.filter(m => m.id !== movieId))); // 注意:这种方式无法自动通知MovieList组件更新状态,需要额外监听localStorage变化 } }; return ( <div> <Card source="saved" savedMovies={savedMovies} setSavedMovies={setSavedMovies} addToMovieList={addToMovieList} /> </div> ); }
这种方案的缺陷是:直接操作localStorage不会触发MovieList组件的状态更新,需要额外添加localStorage监听逻辑,因此还是推荐使用状态提升的方案。
内容的提问来源于stack exchange,提问作者Aiden
相关产品推荐
相关产品推荐

