简体   繁体   中英

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

I am using two components: LandingPage and SearchMovie. SearchMovie component is updating searchTerm (onChange) and passing it to LandingPage(Parent component) which is fetching movies from API. I checked in console.log and SearchTerm state is updating but LandingPage is not re rendering with updated state of searchTerm. How can I do it? I am posting the code here:

**

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;

The searchTerm state in LandingPage changes, but this doesn't trigger any updates to the API data. You defined an onchange function for the search term, but you haven't called it anywhere.

You can redo the search on every keystroke, or you can respond to clicks of a search button and onPressEnter in your search input. I'm going to redo the search on every change. So we've already got searchTerm updating -- we just need to use it!

I think it makes sense to set currentPage before loading data rather than after, but that's just my opinion. That way the effect can respond to changes in the page and the query.

Try this:

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>
  );
}

I would make SearchMenu into a controlled component that both reads and updates the searchTerm state from LandingPage instead of having its own state.

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>
  );
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM