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

React项目中删除收藏电影后列表未实时更新问题求助

问题:React收藏电影列表删除后无法实时更新

开发了一个基于React.js的页面展示用户收藏的电影列表,用户可删除收藏的电影。点击删除按钮能成功将电影从服务器删除,但列表仅在刷新页面后才会移除对应卡片,而非实时更新。项目包含App、SavedMovies、MoviesCardList、MoviesCard组件,删除接口请求正常,尝试过禁用缓存、在SavedMovies组件中添加监听savedMovies状态的useEffect,但均无效果。相关组件代码如下:

function App() {
  const [currentUser, setCurrentUser] = useState({});
  const [isLoggedIn, setIsLoggedIn] = useState(localStorage.getItem('token') ? true : false);
  const [savedMovies, setSavedMovies] = useState([]);

  React.useEffect(() => {
    if (isLoggedIn) {
      Promise.all([mainApi.getUserInfo(), mainApi.getSavedMovies()])
        .then(([data, items]) => {
          const saved = items.filter((m) => m.owner === data._id);

          setCurrentUser(data);
          setSavedMovies(saved);
        })
        .catch((err) => {
          console.log(err);
        })
    }
  }, [isLoggedIn])

  function handleDeleteMovie(movie) {
    const savedMovie = savedMovies.find((m) => {
      return m.movieId === (movie.id || movie.movieId)
    })
    const newSavedMovies = savedMovies.filter((m) => m.movieId !== movie.id)

    mainApi.deleteMovie(savedMovie._id)
      .then(() => {
        setSavedMovies(newSavedMovies);
      })
      .catch((err) => {
        console.log(err);
      })
  }
  return (
    <div className="app">
      <CurrentUserContext.Provider value={currentUser}>
        <Routes>
          <Route path="saved-movies" element={
            <ProtectedRoute isLoggedIn={isLoggedIn}>
              <Header isLoggedIn={isLoggedIn} />
              <SavedMovies
                savedMovies={savedMovies}
                onDeleteMovie={handleDeleteMovie}
              />
              <Footer />
            </ProtectedRoute>
          } />
        </Routes>
      </CurrentUserContext.Provider>
    </div>
  );
}

function SavedMovies({ savedMovies, onDeleteMovie }) {
  const [isShortChecked, setIsShortChecked] = useState(false);
  const [initialMovies, setInitialMovies] = useState(savedMovies);
  const [searchResult, setSearchResult] = useState(initialMovies);

  function filterShortMovies(movies) {
    return movies.filter((movie) =>
      movie.duration <= 40
    )
  }

  function handleFilter(query) {
    const result = savedMovies.filter((movie) => {
      return (
        movie.nameRU.toLowerCase().includes(query.toLowerCase()) ||
        movie.nameEN.toLowerCase().includes(query.toLowerCase())
      )
    })
    setInitialMovies(result);
    setSearchResult(isShortChecked ? filterShortMovies(result) : result);
  }

  function handleShort() {
    setIsShortChecked(!isShortChecked);
    if (!isShortChecked) {
      setSearchResult(filterShortMovies(initialMovies))
    } else {
      setSearchResult(initialMovies)
    }
  }

  function onSearchSubmit(inputData) {
    handleFilter(inputData)
  }

  useEffect(() => {
    setSearchResult(savedMovies)
  }, [savedMovies])

  return (
    <div className='saved-movies'>
      <SearchForm
        onSearch={onSearchSubmit}
        isShort={isShortChecked}
        handleShort={handleShort} />
      <MoviesCardList
        savedMovies={savedMovies}
        onDeleteMovie={onDeleteMovie}
        movies={searchResult}
      />
    </div>
  )
}

function MoviesCard({ movie, onSaveMovie, onDeleteMovie, isSaved }) {
  const location = useLocation();

  const [buttonText, setButtonText] = React.useState('');
  const [buttonClassName, setButtonClassName] = React.useState('');
  const [buttonVisibility, setButtonVisibility] = React.useState('movie-card__button_invisible');

  React.useEffect(() => {
    if (isSaved) {
      setButtonText('');
      setButtonClassName('movie-card__button-delete');
      setButtonVisibility('');
      if (location.pathname === '/saved-movies') {
        setButtonVisibility('movie-card__button_invisible');
      }
    } else {
      setButtonText('Сохранить');
      setButtonClassName('movie-card__button-save');
      setButtonVisibility('movie-card__button_invisible');
    }
  }, [isSaved]
  )

  function handleButtonChange(evt) {
    evt.preventDefault();
    if (isSaved) {
      onDeleteMovie(movie);
      setButtonClassName('movie-card__button-save');
      setButtonText('Сохранить');
    } else {
      onSaveMovie(movie);
      setButtonText('');
      setButtonClassName('movie-card__button-delete');
    }
  }

  function handleDelete(evt) {
    evt.preventDefault();
    onDeleteMovie(movie);
  }

  return (
    <div className='movie-card'>
      <a href={movie.trailerLink} className='movie-card__container' onMouseOver={handleMouseOver} onMouseLeave={handleMouseLeave} target="_blank" rel="noreferrer">
        <img src={ } className='movie-card__image' />
        {
          location.pathname === '/saved-movies' && (
            <button className={`movie-card__button movie-card__button-remove ${buttonVisibility}`} onClick={handleDelete}></button>
          )
        }
      </a>
      <div className='movie-card__description'>
        <p className='movie-card__name'>{movie.name}</p>
        <span className='movie-card__duration'>{`${hours} ${minutes}`}</span>
      </div>
    </div>
  )
}

问题分析与修复方案

1. 删除逻辑中的过滤条件错误

在App组件的handleDeleteMovie函数中,过滤新数组时使用的是m.movieId !== movie.id,但从SavedMovies传入的电影对象,其标识字段应该是movie.movieId(服务器返回的收藏电影结构),而非movie.id。这会导致过滤后的数组没有移除目标电影,状态更新后列表无变化。

修复代码:

function handleDeleteMovie(movie) {
  const savedMovie = savedMovies.find((m) => {
    return m.movieId === (movie.id || movie.movieId);
  });
  // 统一获取目标电影的ID字段
  const targetMovieId = movie.id || movie.movieId;
  const newSavedMovies = savedMovies.filter((m) => m.movieId !== targetMovieId);

  mainApi.deleteMovie(savedMovie._id)
    .then(() => {
      setSavedMovies(newSavedMovies);
    })
    .catch((err) => {
      console.log(err);
    });
}

2. SavedMovies组件状态不同步

SavedMovies中的initialMovies通过useState(savedMovies)初始化,仅在组件挂载时赋值一次,后续savedMovies更新时不会自动同步。而页面展示的searchResult依赖initialMovies,导致删除后searchResult仍基于旧数据渲染。

修复代码:

function SavedMovies({ savedMovies, onDeleteMovie }) {
  const [isShortChecked, setIsShortChecked] = useState(false);
  const [initialMovies, setInitialMovies] = useState(savedMovies);
  const [searchResult, setSearchResult] = useState(initialMovies);

  // 监听savedMovies和isShortChecked变化,同步更新状态
  useEffect(() => {
    setInitialMovies(savedMovies);
    // 根据当前筛选状态更新搜索结果
    setSearchResult(isShortChecked ? filterShortMovies(savedMovies) : savedMovies);
  }, [savedMovies, isShortChecked]);

  // ...其余代码保持不变
}

3. 验证删除按钮的参数传递

确保MoviesCard中点击删除按钮时,传递给onDeleteMovie的movie对象包含正确的movieId或id字段,避免因字段缺失导致过滤逻辑失效。


额外排查建议

  • 删除成功后,在handleDeleteMovie的then回调中打印newSavedMovies和原savedMovies,对比是否正确移除了目标电影。
  • 检查SavedMovies中searchResult的更新逻辑,确保任何状态变化都能同步到最终展示的列表中。

内容的提问来源于stack exchange,提问作者Rubyroido

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:25:02