You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 20:42:34