简体   繁体   中英

Why is my state only updated on the second state change instead of first state change with useEffect in React?

I made a search bar that allows the user to search all sports available in one specific city (if sport is not defined) or a specific sport in a specific city (if sport is defined). City will allways be defined.

I put 2 inputs (city and sport) on my searchbar and I want immediate results (so that there is a request to my API without any button "search" that triggers the request).

So when the user types something on the city input it triggers a request to the API and when he types something on the sport input it retriggers the request but this time with the city and the sport defined. Both inputs values are store in states (city and sport).

I manage to do something that seems to work, the only problem is that if I types a sport in my input search, it does not update my request to the API. I have to retype the sport in my input a second time so that the request is updated. I don't know why it does not update the first time I types something in my sport input because I have specified on my useEffect array that it must re render when the sport state changes.

Can someone help me understand this?

My code:

import React, { useState, useEffect } from "react";
import style from "../styles/pdrs.module.css";
import axios from "axios";


import SearchBar from "../components/SearchBar";

const Pdrs = ({ setSearchCity, searchSport, setSearchSport }) => {
  // if request's result is loading
  const [isLoading, setIsLoading] = useState(false);
  // search result
  const [searchresults, setSearchresults] = useState(
    "Lancez une recherche avec au moins une ville !"
  );
  // state for the searchbar request
  const [city, setCity] = useState("");
  const [sport, setSport] = useState(0);

  // get city's id for API's request
  const fetchCity = async () => {
    setIsLoading(true);
    try {
      // city search
      const cityResponse = await axios.get(
        `${baseAPI}/city/name=${searchCity}`
      );
      const city = cityResponse.data;
      setCity(city);
      setIsLoading(false);
    } catch (error) {
      console.log(error.message);
      setIsLoading(false);
    }
  };

  //fetching sport id
  const fetchSport = async () => {
    setIsLoading(true);
    try {
      const sportResponse = await axios.get(
        `${baseAPI}/activity/name=${searchSport}`
      );
      setSport(sportResponse.data.data[0].macro_activity_id);
      setIsLoading(false);
    } catch (error) {
      console.log(error.message);
    }
  };

  //fetching final request response
  const fetchDataRequest = async () => {
    try {
      setIsLoading(true);
      const results = await axios.get(
        `${baseAPI}/pdrs?city_id=${city.id}${
          sport ? "&macro_activity_id=" + sport : ""
        }`
      );
      // manage search results
      if (results.data.nb_results === 1) {
        setSearchresults({
          data: [results.data.data],
          nb_results: 1,
        });
        setNbResults(1);
        setIsLoading(false);
      } else {
        setSearchresults(results.data);
        setNbResults(results.data.nb_results);
        setIsLoading(false);
      }
    } catch (error) {
      console.log(error.message);
      setSearchresults(
        "Sorry, nothing was found... !"
      );
    }
  };

  useEffect(() => {
    if (searchCity) {
      fetchCity();
    }
    if (searchSport) {
      fetchSport();
    }
  }, [searchCity, searchSport]);

  useEffect(() => {
    if (searchCity) {
      fetchDataRequest();
    }
  }, [searchCity, searchSport]);
  console.log(searchresults);
  return <>
    <main className={`container ${style.pdrs}`}>
      <section className={style.searchbar}>
        <SearchBar
          searchCity={searchCity}
          setSearchCity={setSearchCity}
          searchSport={searchSport}
          setSearchSport={setSearchSport}
          searchInstallation={searchInstallation}
          setSearchInstallation={setSearchInstallation}
          searchType={searchType}
          setSearchType={setSearchType}
          setPage={setPage}
        />
      </section>
      <section className={style.results}>
        {isLoading ? (
          <div>Loading...</div>
        ) : typeof searchresults === "string" ? (
          <div className={`${style.container} ${style.noResults}`}>
            <h2>{searchresults}</h2>
          </div>
        ) : (
          <>
            <div className={style.container}>
              <div className={style.resultsList}>
                {searchresults.data.map((pdrs) => {
                  return (
                 // some code displaying the result
                  );
                })}
              </div>
            </div>
          </>
        )}
      </section>
    </main>
</>;
};

export default Pdrs;

Since you are having two useEffect and one is setting city and sport you would need to make debounce for making a call for fetching list by itself.

I would suggest that you firstly make changes to your use effect for API call fetchDataRequest :

useEffect(() => {
  if (searchCity) {
    fetchDataRequest();
  }
}, [city, sport]);

You would listen to the actual data from BE, not from input that you fill.

And secondly you can use library useDebounce from here https://www.npmjs.com/package/use-debounce and use useDebounceCallback to delay calling API call after you select sport/city.

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