首页 > 解决方案 > 反应 MovieDB API 问题。设置 this.setState 两次会破坏我的组件

问题描述

我遵循了一个使用带有 React 的 MovieDB API 的 youtube 教程。我可以搜索电影并提取标题、描述和电影图像。我想添加播放 youtube 预告片的功能。我已成功获取 youtube ID 并存储它们。我无法显示它们。如果我在 App.js 中取消注释

this.setState({ rows: videoRows });

然后 youtube 视频工作。但是我的标题、描述和电影图像是未定义的,反之亦然。

应用程序.js

import React, { Component } from "react";
import "./App.css";
import MovieRow from "./MovieRow.js";

import $ from "jquery";

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {};
    this.performSearch("woman");
  }

  performSearch(searchTerm) {
    // console.log("perform search");
    const API_KEY = "625914798003ef54176364f32c232968";
    const urlString = `https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${searchTerm}`;
    const urlVideoString = `https://api.themoviedb.org/3/movie/297762/videos?api_key=${API_KEY}&language=en-US`;

    //fetch generic info
    $.ajax({
      url: urlString,
      success: searchResults => {
        console.log("fetch basic success");
        const results = searchResults.results;
        var videoRows = [];
        var movieRows = [];
        // call next ajax function
        $.ajax({
          url: urlVideoString,
          success: searchResults => {
            console.log("fetch Youtube video key success");
            const results = searchResults.results;

            results.forEach(movie => {
              movie.video_src = movie.key;
              console.log(movie.video_src);

              var videoRow = <MovieRow key={movie.id} movie={movie} />;
              videoRows.push(videoRow);
            });


            //If I run this line below it will break 
            //my generic basic info(title, movie description, and picture) , 
            //but it makes the youtube player work
            // this.setState({ rows: videoRows });
          },



          error: (xhr, status, err) => {
            console.log("failed video fetch");
          }
        });
        results.forEach(movie => {
          movie.poster_src =
            "https://image.tmdb.org/t/p/w185" + movie.poster_path;
          console.log(movie.poster_path);

          const movieRow = <MovieRow key={movie.id} movie={movie} />;
          movieRows.push(movieRow);
        });

        this.setState({ rows: movieRows });
      },

      error: (xhr, status, err) => {
        console.log("failed fetch");
      }
    });
  }
  searchChangeHandler(event) {
    console.log(event.target.value);
    const boundObject = this;
    const searchTerm = event.target.value;
    boundObject.performSearch(searchTerm);
  }
  render() {
    return (
      <div>
        <table className="titleBar">
          <tbody>
            <tr>
              <td>
                <img alt="app icon" width="100" src="green_app_icon.svg" />
              </td>
              <td width="8" />
              <td>
                <h1>MoviesDB Search</h1>
              </td>
            </tr>
          </tbody>
        </table>
        <input
          style={{
            fontSize: 24,
            display: "block",
            width: "99%",
            paddingTop: 8,
            paddingBottom: 8,
            paddingLeft: 16
          }}
          onChange={this.searchChangeHandler.bind(this)}
          placeholder="Search for movie by title..."
        />
        {this.state.rows}
      </div>
    );
  }
}

export default App;

电影行.js

import React from "react";
import YouTube from "react-youtube";

class MovieRow extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  viewMovie() {
    const url = "https://www.themoviedb.org/movie/" + this.props.movie.id;
    window.location.href = url;
  }
  viewTrailer() {
    //const trailerURL = "https://www.youtube.com/watch?v=1Q8fG0TtVAY";
  }
  _onReady(event) {
    // access to player in all event handlers via event.target
    event.target.pauseVideo();
  }
  render() {
    const opts = {
      height: "390",
      width: "50%",
      playerVars: {
        // https://developers.google.com/youtube/player_parameters
        autoplay: 1
      }
    };

    return (
      <table key={this.props.movie.id}>
        <tbody>
          <tr>
            <td>
              <img alt="poster" width="180" src={this.props.movie.poster_src} />
            </td>
            <td>
              <h1>src {this.props.movie.video_src}</h1>
              <h3>{this.props.movie.title}</h3>
              <p>{this.props.movie.overview}</p>

              <YouTube
                videoId={this.props.movie.video_src}
                opts={opts}
                onReady={this._onReady}
              />
              <input
                className="btn btn-primary"
                type="button"
                onClick={this.viewTrailer.bind(this)}
                value="Play Trailer"
              />
              <input
                className="btn btn-primary"
                type="button"
                onClick={this.viewMovie.bind(this)}
                value="View"
              />
            </td>
          </tr>
        </tbody>
      </table>
    );
  }
}

export default MovieRow;

标签: ajaxreactjsjsx

解决方案


我看到这里发生了很多问题。一是行状态未初始化。您应该始终在构造函数上初始化一个状态

this.state = {
  row: []
};

我看到的另一个问题是您不应该将组件存储在状态中。看到这个

状态应包含组件的事件处理程序可能更改以触发 UI 更新的数据。在实际应用程序中,这些数据往往非常小并且可以进行 JSON 序列化。在构建有状态组件时,请考虑其状态的最小可能表示形式,并且仅将这些属性存储在 this.state 中。在 render() 内部,只需根据此状态计算您需要的任何其他信息。你会发现以这种方式思考和编写应用程序往往会导致最正确的应用程序,因为向状态添加冗余或计算值意味着你需要显式地保持它们同步,而不是依赖 React 为你计算它们。

你可以做的是重写你的状态,比如

this.state = {
   movies: []
}

在你的 ajax 请求中

// Change these lines to 
results.forEach(movie => {
  movie.video_src = movie.key;
  console.log(movie.video_src);

  var videoRow = <MovieRow key={movie.id} movie={movie} />;
  videoRows.push(videoRow);
});

// To this
results.forEach(movie => {
  movie.video_src = movie.key;
  videoRows.push(movie);
});

this.setState({ movies: videoRows });

最后在您的渲染方法中,循环您状态中的所有电影并返回您的 MovieRow 组件

{this.state.movies.map(movie => <MovieRow key={movie.id} movie={movie}/>)}

推荐阅读