У меня есть функция 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.info(docRef); // => ya {converter: null, _key: ct, type:
// 'document', firestore: ih}
const docSnap = await getDoc(docRef);
console.info(docSnap); // => ya {converter: null, _key: ct, type:
// 'document', firestore: ih}
const dataArr = [];
dataArr.push(docSnap.data());
console.info(dataArr); // => [{…}] (and inside the object is the data:
// {change_keys: Array(53), images: {…}} )
setConfigs((prevData) => {
console.info("setConfigs executed!"); // => NOT EXECUTED!!!
const nextData = {
...prevData,
...dataArr,
};
return nextData;
});
}
getDataFromDB();
}, []);
// ==================================
console.info(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
, поэтому я действительно не понимаю, почему она не устанавливает состояние. Та же операция с получением данных из апи работает, например при использовании fetch().then().then().catch(
). Я пробовал .then()
цепочку и здесь, но безрезультатно. Прочитал много статей в гугле, но для моего случая не подходит, может внутри всего моего кода что-то есть? Итак, мой вопрос: как правильно установить состояние при получении данных из хранилища?
Текущая реализация явно не возвращает значение, поэтому следующим значением состояния будет undefined
. Вам нужно вернуть следующее вычисленное значение состояния.
setConfigs((prevData) => {
console.info("Running setConfigs!!!!!!!!!!!!!!!!!");
const nextData = {
...prevData,
...dataArr,
};
return nextData; // <-- return next state value
});
Более кратко:
setConfigs((prevData) => ({
...prevData,
...dataArr,
}));
@rigan Предполагая, что doc.data()
является определенным значением, я не вижу причин, по которым setConfig(doc.data())
не работает. Как именно вы проверяете обновления состояния?
Я вхожу в состояние конфигов консоли. console.info(configs), и я также делаю вход в консоль внутри setTimeout() с 5 секундами
Обновления состояния @rigan React обрабатываются асинхронно. Где именно вы пытаетесь консольно регистрировать обновления? Если вы пытаетесь зарегистрировать состояние сразу после постановки в очередь обновления состояния, которое может регистрировать только текущее значение состояния, закрытое в области обратного вызова.
Я регистрирую их вне useEffect().
Если состояние обновляется, оно должно отображаться в обновлениях консоли, но остается неопределенным.
@rigan Что получается из console.info("dataArr has data from db", dataArr);
? есть значение dataArr
? Вы видите вывод журнала console.info("Running setConfigs!!!!!!!!!!!!!!!!!");
? Каков результат console.info("Document data:", docSnap.data());
?
console.info("dataArr содержит данные из базы данных", dataArr); => показывает данные из firestore, поэтому есть значение dataArr.
console.info("Данные документа:", docSnap.data()); => показывает данные из firestore
Давайте продолжим обсуждение в чате.
Проблема заключалась в том, что установленное состояние работало, но по каким-то причинам другой повторный рендеринг не запускался, и он закончился так, как будто он не был назначен. Затем я использовал состояние, чтобы вызвать другой рендеринг, и это сработало. Итак, мой урок из этого случая состоит в том, чтобы помнить, что 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);
});
}
Хорошо. Попробую, но думаю изменений не будет. Есть кое-что еще. Я пробовал setConfig(doc.data()) и тоже не работает.