firebase 为什么setState不能在嵌套在useEffect中的异步函数中工作?

q9yhzks0  于 2023-01-14  发布在  其他
关注(0)|答案(2)|浏览(154)

我有一个async函数**(getDataFromDB)**,我在useEffect中运行它。该函数从firestore获取数据。问题是我无法从firebase为数据设置状态。我获取了对数据的引用,我可以通过记录在控制台中看到数据,但setConfigs不起作用,configs状态仍然未定义。

// App

// imports
import { db } from "./firebase-config";
import {
  query,
  doc,
  collection,
  onSnapshot,
  getDocs,
  getDoc,
  Firestore,
} from "firebase/firestore";
// from packages
import { useState, useEffect, useRef, useCallback } from "react";
import { Outlet } from "react-router-dom";
// sections
import Navbar from "./components/sections/Navbar";
import SearchBar from "./components/sections/Search-bar";
import TrendingBar from "./components/sections/Trending-bar";
// context
import ContextProviders from "./context-config";
// utils
import {
  getGenres,
  getTrendingData,
  getMovies,
  getTv,
  onStartIntoDB,
  getDataFromDB,
} from "./utils/fetchData";
// global constants
import { MAP_URL } from "./data/global-constants";
import { FirebaseError } from "firebase/app";

export default function App() {
  // States
  // genres and configs
  const [genres, setGenres] = useState([]);
  
  const [loading, setLoading] = useState("true");
  
  // ==============================
  // HERE IS CONFIGS STATE DECLARED
  // ==============================
  const [configs, setConfigs] = useState([]);

  // movies data
  const [trendingData, setTrendingData] = useState([]);
  const [moviesData, setMoviesData] = useState([]);
  const [popularMoviesData, setPopularMoviesData] = useState([]);
  const [topRatedMoviesData, setTopRatedMoviesData] = useState([]);
  const [upcomingMoviesData, setUpcomingMoviesData] = useState([]);
  const [nowPlayingMoviesData, setNowPlayingMoviesData] = useState([]);
  // tv data
  const [tvData, setTvData] = useState([]);
  const [airingTodayTvData, setAiringTodayTvData] = useState([]);
  const [onTheAirTvData, setOnTheAirTvData] = useState([]);
  const [popularTvData, setPopularTvData] = useState([]);
  const [topRatedTvData, setTopRatedTvData] = useState([]);

  // Refs
  const preventEffect = useRef(true);

  // ---------------------------------------
  // Fetch data from api and push it into db
  // ---------------------------------------

  // configs
  useEffect(() => {
    onStartIntoDB(
      preventEffect,
      "getConfigs",
      MAP_URL.configuration.base_url,
      null,
      null,
      process.env.REACT_APP_API_KEY,
      null,
      "relating-data",
      "configs"
    );
  }, []);

  // genres for movies
  useEffect(() => {
    onStartIntoDB(
      preventEffect,
      "getGenres",
      MAP_URL.genres.base_url,
      MAP_URL.genres.media_type.movie,
      null,
      process.env.REACT_APP_API_KEY,
      null,
      "relating-data",
      "genres-movie"
    );
  }, []);

  // genres for tv
  useEffect(() => {
    onStartIntoDB(
      preventEffect,
      "getGenres",
      MAP_URL.genres.base_url,
      MAP_URL.genres.media_type.tv,
      null,
      process.env.REACT_APP_API_KEY,
      null,
      "relating-data",
      "genres-tv"
    );
  }, []);

  // trending movies
  useEffect(() => {
    onStartIntoDB(
      preventEffect,
      "getTrendingData",
      MAP_URL.trendingMovies.base_url,
      MAP_URL.trendingMovies.media_type.all,
      MAP_URL.trendingMovies.time_window.week,
      process.env.REACT_APP_API_KEY,
      null,
      "media",
      "trending-movies"
    );
  }, []);

  // movies
  useEffect(() => {
    onStartIntoDB(
      preventEffect,
      "getMovies",
      MAP_URL.movies.base_url,
      null,
      null,
      process.env.REACT_APP_API_KEY,
      MAP_URL.movies.lang_and_page,
      "media",
      "movies"
    );
  }, []);

  // tv
  useEffect(() => {
    onStartIntoDB(
      preventEffect,
      "getTv",
      MAP_URL.tv.base_url,
      null,
      null,
      process.env.REACT_APP_API_KEY,
      MAP_URL.tv.lang_and_page,
      "media",
      "tv"
    );
  }, []);
   
   // =================================================================
   // Right below is the useEffect that should update the state with an
   // async funtion. 
   // =================================================================   
useEffect(() => {
   async function getDataFromDB() {
      const docRef = doc(db, "relating-data", "configs");
      console.log(docRef); // => ya {converter: null, _key: ct, type: 
                           //   'document', firestore: ih}

      const docSnap = await getDoc(docRef);
      console.log(docSnap); // => ya {converter: null, _key: ct, type: 
                            //    'document', firestore: ih}

      const dataArr = []; 

      dataArr.push(docSnap.data());
      console.log(dataArr); // => [{…}] (and inside the object is the data: 
                            //    {change_keys: Array(53), images: {…}} )
 
      setConfigs((prevData) => {
        console.log("setConfigs executed!"); // => NOT EXECUTED!!!
        const nextData = {
          ...prevData,
          ...dataArr,
        };
        return nextData;
      });
    }
    getDataFromDB();
  }, []);

  // ==================================
  console.log(configs); // => undefined
  // ==================================

  // genres
  useEffect(() => {
    async function g() {
      const data = await getGenres(
        MAP_URL.genres.base_url,
        MAP_URL.genres.media_type.movie,
        process.env.REACT_APP_API_KEY
      );
      setGenres(data);
    }
    g();
  }, []);

  // trending movies
  useEffect(() => {
    return async function () {
      const data = await getTrendingData(
        MAP_URL.trendingMovies.base_url,
        MAP_URL.trendingMovies.media_type.all,
        MAP_URL.trendingMovies.time_window.week,
        process.env.REACT_APP_API_KEY
      );
      setTrendingData(data.results);
    };
  }, []);
  // movies
  useEffect(() => {
    return async function () {
      const data = await getMovies(
        MAP_URL.movies.base_url,
        process.env.REACT_APP_API_KEY,
        MAP_URL.movies.lang_and_page
      );
      setMoviesData(data);
    };
  }, []);

  // tv
  useEffect(() => {
    return async function () {
      const data = await getTv(
        MAP_URL.tv.base_url,
        process.env.REACT_APP_API_KEY,
        MAP_URL.tv.lang_and_page
      );
      setTvData(data);
    };
  }, []);

  // ----------------------------------
  // set popular movies
  useEffect(() => {
    setPopularMoviesData(moviesData[0]);
  }, [moviesData[0]]);
  // set top-rated movies
  useEffect(() => {
    setTopRatedMoviesData(moviesData[1]);
  }, [moviesData[1]]);
  // set now-playing movies
  useEffect(() => {
    setNowPlayingMoviesData(moviesData[2]);
  }, [moviesData[2]]);
  // set upcoming movies
  useEffect(() => {
    setUpcomingMoviesData(moviesData[3]);
  }, [moviesData[3]]);
  // ----------------------------------
  // set airing-today tv
  useEffect(() => {
    setAiringTodayTvData(tvData[2]);
  }, [tvData[2]]);
  // set on-the-air tv
  useEffect(() => {
    setOnTheAirTvData(tvData[3]);
  }, [tvData[2]]);
  // set popular tv
  useEffect(() => {
    setPopularTvData(tvData[0]);
  }, [tvData[0]]);
  // set top-rated tv
  useEffect(() => {
    setTopRatedTvData(tvData[1]);
  }, [tvData[1]]);
  // ----------------------------------

  return (
    <ContextProviders configs={configs} trendingData={trendingData}>
      <div className="App">
        <Navbar />
        <SearchBar />
        <TrendingBar />
        <Outlet />
      </div>
    </ContextProviders>
  );
}

我尝试从async函数外部设置状态,但是在这个例子中我调用了另一个async,所以我真的不明白为什么它不设置状态。从API获取数据的操作也是一样的,例如如果使用fetch().then().then().catch()。我也尝试了.then()链,但是没有任何结果。我在google上读了很多文章,但它不适用于我的情况,也许在我的整个代码中有一些东西?所以我的问题是如何正确地设置状态时,从一个firestore获取数据?

db2dz4w8

db2dz4w81#

当前实现没有显式返回值,因此下一个状态值将是undefined。您需要返回下一个计算的状态值。

setConfigs((prevData) => {
  console.log("Running setConfigs!!!!!!!!!!!!!!!!!");

  const nextData = {
    ...prevData,
    ...dataArr,
  };

  return nextData; // <-- return next state value
});

更简洁地说:

setConfigs((prevData) => ({
  ...prevData,
  ...dataArr,
}));
tyg4sfes

tyg4sfes2#

问题是-设置的状态工作,但由于某些原因,没有触发另一个重新渲染,它结束了就像它没有被分配。然后我消费的状态,以触发另一个渲染,它的工作。所以我从这个案例的教训是要记住,useState是异步的,确保重新渲染,以查看状态。
以下是对我有效的解决方案:

export async function getDataFromDB(setState, collection_name) {
  await getDocs(collection(db, collection_name)).then((querySnapshot) => {
    const newData = querySnapshot.docs.map((doc) => ({
      ...doc.data(),
      id: doc.id,
    }));
    setState(newData);
  });
}

相关问题