Pull data from firestore using useEffect works on re-render only - reactjs

Here is my code:
import React, { useEffect, useState } from 'react';
import { getDocs, collection } from 'firebase/firestore';
import { db } from '../firebase-config';
const Home = () => {
const [postList, setPostList] = useState([]);
const postsCollectionRef = collection(db, "data");
useEffect(() => {
const getPosts = async () => {
const data = await getDocs(postsCollectionRef);
let postListArray = []
data.forEach((doc) => {
const post = { ...doc.data() }
postListArray.push(post)
});
setPostList(postListArray)
};
getPosts();
console.log(postList);
}, []);
return (
<div>test</div>
);
};
export default Home;
On loading, the console.log returned an empty array. The spooky thing is when i changed anything , for example
return (
<div>test_epic</div>
);
The console.log shows that it is an array. Anyone has any idea as to why? Please refer to the screepcap as attached.
the first render on loading
I changed anything and components rerendered

Setting state in react is asynchronous, so the data is loaded and the state is set but the console.log statement is executed before the setting state async operation is complete
To make it a bit more clear this is how it works step by step
Component is rendered and postList is initialized with a value of []
useEffect is triggered
Data is fetched
A call to set a new value of postList is placed using setPostList (key here is a call is placed not that the data is actually updated)
You print console.log with a value from Step 1
The call from Step 4 is complete and now the data is actually updated
Here is an article that explains it with examples
And here is another answer that explains this deeply

Related

Trying to use isPending in React 18 without Suspense and not getting expected results

I understand that the isPending return in React 18 is used so that lower priority state updates can happen last. I'm struggling to figure out how to make isPending work in a simple REST GET call example. I'm trying to write the code below without having to use a separate isLoading type state.
Is there anything I can do to make this happen? That is, with only isPending render a "loading..." message until the data has been retrieved?
(the results I get from the code below is I see a flash of "loading", then immediately see [] followed by my data. I want to see "loading" until my data actually loads)
import axios from "axios";
import { useEffect, useState, useTransition } from "react";
export default function Test1() {
const [data, setData] = useState([])
const [isPending, startTransition] = useTransition();
useEffect(() => {
async function fetchMyAPI() {
startTransition(async () => {
const results = await axios.get("/api/rest");
setData(results.data);
})
}
fetchMyAPI()
}, [])
if (isPending) return <div>Loading...</div>
return (
<div>{JSON.stringify(data)}</div>
);
}

React not state not updating first time

So this is my code:
const [module, setModule] = useState([]);
useEffect(()=> {
async function getModuleInfo(){
let ModuleInfo = await firebase
.firestore()
.collection('Modules')
.doc('PBS1Module1')
.get();
if (!ModuleInfo.exists){
console.log('geen module info')
} else {
let ModuleInfov2 = ModuleInfo.data();
setModule(ModuleInfov2)
}} getModuleInfo()
console.log(module)
}, [])
When I go to this screen, the first log is an empty array. Then when I remove the console.log() and save it and than change it back to console.log(module) it gives me the data I need.
What am I doing wrong? All my import statements are good because those are working.
This is because when you are logging out the value module inside the useEffect hook you now have a stale closure. When the component initially renders, the value of module is [] so that is what is passed to the closure inside useEffect. When you update the state via the setModule function, React will rerender the component with the updated state and you get the expected value. To help understand this, try moving your console.log outside of the useEffect. This will make it run every time the component renders, as opposed to now where it only runs on the first render since your dependency array on the useEffect hook is empty.
I tested with the below example and when running it I get logs in the following order:
fresh data is: null
stale data is: null
fresh data is: (4) [1, 2, 3, 4]
import { useEffect, useState } from "react";
const fetchData = async () => {
return [1, 2, 3, 4, ]
}
export const App = () => {
const [data, setData] = useState(null)
useEffect(() => {
const asyncFetch = async () => {
const data = await fetchData();
setData(data);
}
asyncFetch();
console.log("stale data is: ", data);
}, []);
console.log("fresh data is: ", data);
return (
<div>
</div>
)
}

i get the data back from the backend (express.js) but i cannot store it in a state variable

import { useQuery, gql } from "#apollo/client";
import { getbooks } from "../../GraphQl/Queries";
function BookList() {
const { error, loading, data } = useQuery(getbooks);
const [book, setbook] = useState([]);
useEffect(() => {
if (data) {
console.log(data.books);
setbook(data.books);
console.log(book);
}
}, [data]);
return (
<div>
<ul className="book-list"></ul>
</div>
);
}
export default BookList;
here, console.log(data.books); is actually working and I get back the list I wanted
but when I try to store it in a state variable, I get back an empty list
how do I rectify this problem pls help
Setting the state is asynchronous so you can't log it inside the function right after you set it, you won't see it. So in this case you're actually setting the state after you log it to the console.
Put the console.log outside of your useEffect, you'll see the data.

useState setter not updating state when called in useEffect

Pretty much what it says on the title. When I console.log(repos) it returns an empty array. Why is the repos state not updating?
import React, { useEffect, useState } from "react";
import axios from "axios";
export default () => {
const [repos, setRepos] = useState([]);
useEffect(() => {
(async () => {
try {
let repo_lists = await axios.get(
"https://api.github.com/users/Coddielam/repos"
// { params: { sort: "created" } }
);
setRepos(repo_lists.data.slice(1).slice(-10));
console.log(repo_lists.data.slice(1).slice(-10));
console.log(repos);
} catch (err) {
setRepos([
"Something went wrong while fetching GitHub Api./nPlease use the link below to view my git repos instead ",
]);
}
})();
}, []);
return (
<div className="content">
<h2>View my recent git repos:</h2>
<ul>
...
</ul>
</div>
);
};
Answer is very simple. Your useState is updating .. believe me. The reason why you don't see it when you console.log() is because SetRespos is an asynchronous function.
Basically when you declare a function to update you useState value, react will use it as an async function
EXAMPLE
const [example, setExample] = useState('');
useEffect(() => {
setExample('Hello');
console.log('I'm coming first'); // This will be executed first
console.log(example); // This will come after this
}, [])
The output will be :
I'm coming first
// Blank Line
But still your useState will update after this. If you want to see that do this :
useEffect(() => {
console.log(respose); // This will give you the value
}, [respos])
I'm using a separate useEffect to console.log() the value. In the [] (dependency array) we pass respos which simply means that the useEffect will run every time the value of respos changes.
Read more about useStates and useEffects in react's documentation
State updates are async. You will only see them reflected on the next render. If you console log the state immediately after calling setState it will always log the current state, not the future state.
You can log the state in an effect every time it changes and you will see it changing:
useEffect(() => console.log(repos), [repos]);
This effect will be called after the state update has been applied.

how to make an object with multiple object an array in state?

This is my code,
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
function App() {
let [albums, setAlbums] = useState([]);
useEffect(() => {
const key = "blablabla to keep secret";
const fetchData = async () => {
const result = await axios(
`http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=cher&api_key=${key}&limit=10&format=json`
);
setAlbums(result.data.topalbums);
console.log(albums, "data?");
// const { data } = props.location.state;
};
fetchData();
}, []);
return <div className="App"></div>;
}
export default App;
the data I am fetching is in an object with objects inside, in-state I initialized with an empty array, and then after I fetched the data I use setAlbums. I have two problems, the console.log after I fetch just shows me an empty array, but when I console log in render I do get the data but it is an object and not an array of objects, so I can't even map over it, how do I fix this?
Try to do something like this:
setAlbums(album => [...album, results.data.topalbums])
that way you can push results to your array instead of transforming it into object
also if you wish to see updated album then create something like:
useEffect(() => {
console.log(albums)
},[albums])
as setting state is asynchronous therefore it doesn't happen immediately as Brian mentioned, so this code will launch if albums state changes its value

Resources