使用状态未按预期更新

Use State not updating as expected

反应还很新,正在尝试构建 The Movie Database 站点的克隆。我想要这个切换开关将我的 api 通话从电影切换到电视。单击几下后它开始工作,但随后它会抛出所有内容并且无论如何都不会显示正确的项目。不太确定这里发生了什么......甚至不知道为什么它在点击两次后开始工作。有人知道这是怎么回事吗?

import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";

const Popular = ({ imageUri }) => {
  // GET POPULAR MOVIES
  const [popularMovies, setPopularMovies] = useState("");
  const [genre, setGenre] = useState("movie");
  console.log(genre);

  const getPopular = async () => {
    const response = await axios.get(
      `https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
    );
    setPopularMovies(response.data.results);
  };

  useEffect(() => {
    getPopular();
  }, []);

  const listOptions = document.querySelectorAll(".switch--option");
  const background = document.querySelector(".background");

  const changeOption = (el) => {
    let getGenre = el.target.dataset.genre;
    setGenre(getGenre);
    getPopular();

    listOptions.forEach((option) => {
      option.classList.remove("selected");
    });

    el = el.target.parentElement.parentElement;
    let getStartingLeft = Math.floor(
      listOptions[0].getBoundingClientRect().left
    );
    let getLeft = Math.floor(el.getBoundingClientRect().left);
    let getWidth = Math.floor(el.getBoundingClientRect().width);
    let leftPos = getLeft - getStartingLeft;
    background.setAttribute(
      "style",
      `left: ${leftPos}px; width: ${getWidth}px`
    );
    el.classList.add("selected");
  };

  return (
    <section className="container movie-list">
      <div className="flex">
        <div className="movie-list__header">
          <h3>What's Popular</h3>
        </div>
        <div className="switch flex">
          <div className="switch--option selected">
            <h3>
              <a
                data-genre="movie"
                onClick={(e) => changeOption(e)}
                className="switch--anchor"
              >
                In Theaters
              </a>
            </h3>
            <div className="background"></div>
          </div>
          <div className="switch--option">
            <h3>
              <a
                data-genre="tv"
                onClick={(e) => changeOption(e)}
                className="switch--anchor"
              >
                On TV
              </a>
            </h3>
          </div>
        </div>
      </div>
      <div className="scroller">
        <div className="flex flex--justify-center">
          <div className="flex flex--nowrap container u-overScroll">
            {popularMovies &&
              popularMovies.map((movie, idX) => (
                <div key={idX} className="card">
                  <div className="image">
                    <img src={imageUri + "w500" + movie.poster_path} />
                  </div>
                  <p>{movie.title}</p>
                </div>
              ))}
          </div>
        </div>
      </div>
    </section>
  );
};

export default Popular;

您在映射数组时使用数组索引作为关键道具。

您应该使用特定于您正在呈现的数据的 ID。 React 使用 key 属性来了解自上次渲染以来哪些项目发生了变化。

在您的情况下,您应该在关键道具中使用电影 ID 而不是数组索引。

popularMovies.map((movie) => (
    <div key={movie.id} className="card">
      <div className="image">
        <img src={imageUri + 'w500' + movie.poster_path} />
      </div>
      <p>{movie.title}</p>
    </div>
));

还有

您是在 setGenre 之后直接调用 api。但是状态更改不是立即的。因此,当您拨打 api 电话时,您仍在发送最后一部电影类型。

两种解决方法:

您可以直接使用流派调用您的函数,并更改您的函数以处理这种情况:

getPopular('movie');

或者您根本无法调用该函数并将流派添加为您的 useEffect 的依赖项。这样,每次类型改变时 useEffect 都会 运行。

useEffect(() => {
    getPopular();
}, [genre]);

PS:您应该考虑将您的代码拆分为更多组件,而不是直接与 DOM 交互。

为了让您了解它的外观,我进行了一些重构,但还可以进行更多改进:

const Popular = ({ imageUri }) => {
    const [popularMovies, setPopularMovies] = useState('');
    const [genre, setGenre] = useState('movie');

    const getPopular = async (movieGenre) => {
        const response = await axios.get(
            `https://api.themoviedb.org/3/discover/${movieGenre}?sort_by=popularity.desc&api_key=${API_KEY}`
        );
        setPopularMovies(response.data.results);
    };

    useEffect(() => {
        getPopular();
    }, [genre]);


    const changeHandler = (el) => {
        let getGenre = el.target.dataset.genre;
        setGenre(getGenre);
    };

    const isMovieSelected = genre === 'movie';
    const isTvSelected = genre === 'tv';
    return (
        <section className="container movie-list">
            <div className="flex">
                <MovieHeader>What's Popular</MovieHeader>
                <div className="switch flex">
                    <Toggle onChange={changeHandler} selected={isMovieSelected}>
                        In Theaters
                    </Toggle>
                    <Toggle onChange={changeHandler} selected={isTvSelected}>
                        On TV
                    </Toggle>
                </div>
            </div>
            <div className="scroller">
                <div className="flex flex--justify-center">
                    <div className="flex flex--nowrap container u-overScroll">
                        {popularMovies.map((movie) => {
                            const { title, id, poster_path } = movie;
                            return (
                                <MovieItem
                                    title={title}
                                    imageUri={imageUri}
                                    key={id}
                                    poster_path={poster_path}
                                />
                            );
                        })}
                    </div>
                </div>
            </div>
        </section>
    );
};

export default Popular;

const Toggle = (props) => {
    const { children, onChange, selected } = props;
    const className = selected ? 'switch--option selected' : 'switch--option';
    return (
        <div className={className}>
            <h3>
                <a
                    data-genre="movie"
                    onClick={onChange}
                    className="switch--anchor"
                >
                    {children}
                </a>
            </h3>
            <div className="background"></div>
        </div>
    );
};
const MovieHeader = (props) => {
    const { children } = props;
    return (
        <div className="movie-list__header">
            <h3>{children}</h3>
        </div>
    );
};

const MovieItem = (props) => {
    const { title, imageUri, poster_path } = props;
    return (
        <div key={idX} className="card">
            <div className="image">
                <img src={imageUri + 'w500' + poster_path} />
            </div>
            <p>{title}</p>
        </div>
    );
};