为什么即使在 React JS 中更改状态后,我的代码也没有从 API 中获取更新的详细信息?

Why my code is not fetching updated details from API's even after changing state in React JS?

我正在使用两个组件:LandingPage 和 SearchMovie。 SearchMovie 组件正在更新 searchTerm (onChange) 并将其传递给从 API 获取电影的 LandingPage(父组件)。我签入 console.log 并且 SearchTerm 状态正在更新,但 LandingPage 未使用更新的 searchTerm 状态重新呈现。我该怎么做?我在这里发布代码:

**

LandingPage code:

**

import React, { useEffect, useState, useRef } from 'react'
import { Typography, Row } from 'antd';
import { API_URL, API_KEY, IMAGE_BASE_URL, IMAGE_SIZE, POSTER_SIZE } from '../../Config'
import MainImage from './Sections/MainImage'
import GridCard from '../../commons/GridCards'
import SearchMenu from '../LandingPage/Sections/SearchMenu'
const { Title } = Typography;


function LandingPage(props) {

    const [searchTerm, setSearchTerm] = useState('');
    console.log("searchIitialTerm = " + searchTerm);

    const buttonRef = useRef(null);

    const [Movies, setMovies] = useState([])
    const [MainMovieImage, setMainMovieImage] = useState(null)
    const [Loading, setLoading] = useState(true)
    const [CurrentPage, setCurrentPage] = useState(0)

    console.log(props.SearchMenu);
    console.log("searchTermLanding = " + searchTerm);
    var path;
    var loadpath;
    
    onchange = (searchTerm) => {
        if (searchTerm != '') {

            path = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=1`;  
        
            loadpath = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}&page=${CurrentPage + 1}`;            
            
        }
        else if (searchTerm == '') {
            
            path = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
            
            loadpath = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${CurrentPage + 1}`;
                
            
        }

    }
    

    useEffect(() => {
        const endpoint = path;
        fetchMovies(endpoint)
    }, [])

    useEffect(() => {
        window.addEventListener("scroll", handleScroll);
    }, [])


    const fetchMovies = (endpoint) => {

        fetch(endpoint)
            .then(result => result.json())
            .then(result => {
                // console.log(result)
                // console.log('Movies',...Movies)
                // console.log('result',...result.results)
                setMovies([...Movies, ...result.results])
                setMainMovieImage(MainMovieImage || result.results[0])
                setCurrentPage(result.page)
            }, setLoading(false))
            .catch(error => console.error('Error:', error)
            )
    }

    const loadMoreItems = () => {
        let endpoint = '';
        setLoading(true)
        console.log('CurrentPage', CurrentPage)
        endpoint = loadpath;
        fetchMovies(endpoint);
    }

    const handleScroll = () => {
        const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
        const body = document.body;
        const html = document.documentElement;
        const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
        const windowBottom = windowHeight + window.pageYOffset;
        if (windowBottom >= docHeight - 1) {

            // loadMoreItems()
            console.log('clicked')
            buttonRef.current.click();
        }
    }



    return (

        <div>
            <div className="menu__container menu_search">
                <SearchMenu mode="horizontal" onChange={value => setSearchTerm(value)} />
            </div>

            <div style={{ width: '100%', margin: '0' }}>
                {MainMovieImage &&
                    <MainImage
                        image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${MainMovieImage.backdrop_path}`}
                        title={MainMovieImage.original_title}
                        text={MainMovieImage.overview}
                    />

                }

                <div style={{ width: '85%', margin: '1rem auto' }}>

                    <Title level={2} > Latest movies </Title>
                    <hr />
                    <Row gutter={[16, 16]}>
                        {Movies && Movies.map((movie, index) => (
                            <React.Fragment key={index}>
                                <GridCard
                                    image={movie.poster_path ?
                                        `${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
                                        : null}
                                    movieId={movie.id}
                                    movieName={movie.original_title}
                                />
                            </React.Fragment>
                        ))}
                    </Row>

                    {Loading &&
                        <div>Loading...</div>}

                    <br />
                    <div style={{ display: 'flex', justifyContent: 'center' }}>
                        <button ref={buttonRef} className="loadMore" onClick={loadMoreItems}>Load More</button>
                    </div>
                </div>

            </div>

        </div>

    )
}

export default LandingPage

**

SearchMenu code:

**

import React, { useState } from 'react';
import { Route, Switch } from "react-router-dom";
import { Menu } from 'antd';
import { Input } from 'antd';
//import LandingPage from '../../LandingPage/LandingPage';
import '../../NavBar/Sections/Navbar.css';

const SearchMenu = (props) => {
    console.log("props = " + props);
    const [searchTerm, setSearchTerm] = useState("");
    const { Search } = Input;
    const onSearch = value => console.log(value);

    function searchChangeHandler(e) {
        e.preventDefault();
        console.log(e.target.value);
        setSearchTerm(e.target.value);
        props.onChange(e.target.value);
    }
    console.log("searchTerm = " + searchTerm);
    //console.log(props.onChange);

    return (
        <div className="searchMenu">
            <Menu mode={props.mode} />

            <Search
                placeholder="Search"
                allowClear onSearch={onSearch}
                style={{ width: 400 }}
                onChange={(e) => searchChangeHandler(e)}
            />
            

            {/*
            console.log("Search Term = " + searchTerm);
           <LandingPage search={searchTerm}/>
            */}
        </div>
    )
}

export default SearchMenu;

LandingPage 中的 searchTerm 状态发生变化,但这不会触发对 API 数据的任何更新。您为搜索词定义了一个 onchange 函数,但您没有在任何地方调用它。

您可以在每次击键时重新进行搜索,或者您可以响应搜索按钮的点击和搜索输入中的 onPressEnter。我将在每次更改时重新搜索。所以我们已经 searchTerm 更新了——我们只需要使用它!

我认为在加载数据之前而不是之后设置currentPage是有意义的,但这只是我的意见。这样效果就可以响应页面和查询的变化。

试试这个:

function LandingPage() {
  const [searchTerm, setSearchTerm] = useState("");
  const [movies, setMovies] = useState([]);
  const [mainMovieImage, setMainMovieImage] = useState(null);
  const [loading, setLoading] = useState(true);
  const [currentPage, setCurrentPage] = useState(1);

  // do you need this?  could just call loadMoreItems() instead of click()
  const buttonRef = useRef(null);

  const loadMoreItems = () => {
    // just set the page, the effect will respond to it
    setCurrentPage((page) => page + 1);
  };

  const onChangeSearch = (value) => {
    // reset page to 1 when changing search
    setSearchTerm(value);
    setCurrentPage(1);
  };

  // run effect to load movies when the page or the searchTerm changes
  useEffect(() => {
    const endpoint =
      searchTerm === ""
        ? `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${currentPage}`
        : `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${encodeURIComponent(
            searchTerm
          )}&page=${currentPage}`;

    // could use async/await but promise/then is fine too
    setLoading(true);
    fetch(endpoint)
      .then((response) => response.json())
      .then((json) => {
        // replace state on page 1 of a new search
        // otherwise append to exisiting
        setMovies((previous) =>
          currentPage === 1 ? json.results : [...previous, ...json.results]
        );
        // only replace if not already set
        // when should we reset this?
        setMainMovieImage((previous) => previous || json.results[0]);
      })
      .catch((error) => console.error("Error:", error))
      .finally(() => setLoading(false));
  }, [searchTerm, currentPage]);

  const handleScroll = () => {
    const windowHeight =
      "innerHeight" in window
        ? window.innerHeight
        : document.documentElement.offsetHeight;
    const body = document.body;
    const html = document.documentElement;
    const docHeight = Math.max(
      body.scrollHeight,
      body.offsetHeight,
      html.clientHeight,
      html.scrollHeight,
      html.offsetHeight
    );
    const windowBottom = windowHeight + window.pageYOffset;
    if (windowBottom >= docHeight - 1) {
      // loadMoreItems()
      console.log("clicked");
      buttonRef.current?.click();
    }
  };

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    // cleanup function
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <div>
      <div className="menu__container menu_search">
        <SearchMenu
          mode="horizontal"
          value={searchTerm}
          onChange={onChangeSearch}
        />
      </div>

      <div style={{ width: "100%", margin: "0" }}>
        {mainMovieImage && (
          <MainImage
            image={`${IMAGE_BASE_URL}${IMAGE_SIZE}${mainMovieImage.backdrop_path}`}
            title={mainMovieImage.original_title}
            text={mainMovieImage.overview}
          />
        )}

        <div style={{ width: "85%", margin: "1rem auto" }}>
          <Title level={2}> Latest movies </Title>
          <hr />
          <Row gutter={[16, 16]}>
            {movies &&
              movies.map((movie, index) => (
                <React.Fragment key={index}>
                  <GridCard
                    image={
                      movie.poster_path
                        ? `${IMAGE_BASE_URL}${POSTER_SIZE}${movie.poster_path}`
                        : null
                    }
                    movieId={movie.id}
                    movieName={movie.original_title}
                  />
                </React.Fragment>
              ))}
          </Row>

          {loading && <div>Loading...</div>}

          <br />
          <div style={{ display: "flex", justifyContent: "center" }}>
            <button
              ref={buttonRef}
              className="loadMore"
              onClick={loadMoreItems}
              disabled={loading} // disable button when fetching results
            >
              Load More
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

我会把 SearchMenu 变成一个受控组件,它可以从 LandingPage 读取和更新 searchTerm 状态,而不是拥有自己的状态。

const SearchMenu = ({ mode, value, onChange }) => {
  return (
    <div className="searchMenu">
      <Menu mode={mode} />

      <Search
        value={value}
        placeholder="Search"
        allowClear
        style={{ width: 400 }}
        onChange={(e) => onChange(e.target.value)}
      />
    </div>
  );
};