Why when using setState(...) is the console.log null? - javascript

Why when I console.log(data) is does it log the data but when I try and log the item set by set state its null? I don't understand is it because it doesn't have time to update before console logging?
const [currentUser, setCurrentUser] = useState(null);
const [pending, setpending] = useState(true);
const [userData, setuserData] = useState({});
useEffect(() => {
Authentication.auth().onAuthStateChanged((user) => {
setCurrentUser(user)
setpending(false)
localStorage.setItem('user', user.uid);
console.log(localStorage.getItem('user'));
});
getData()
}, []);
const getData = () => {
Authentication.firestore().collection('Health_data')
.doc(localStorage.getItem('user'))
.get()
.then(doc => {
const data = doc.data();
localStorage.setItem('user_data', JSON.stringify(data));
setuserData(data)
console.log(data)
}).catch(function (error) {
console.error("Error reading health", error);
});
console.log(userData)
}

I'm going to assume you're confused by the output of console.log(userData); at the end of getData. There are two reasons why it doesn't show the updated user, either of which would be enough on its own. :-) They are:
The console.log(userData) happens before the setuserData call, because promise callbacks are always asynchronous.
userData is a constant within the Example function call, its value will not change. When the state is changed, Example will get called again, and that new userData constant will have the updated user in it. (Note that it doesn't matter whether it's declared with let or const, it's effectively a constant either way.) Each of those constants exists in an execution context that's tied to the specific call to Example the constant was created in. More in Dan Abramov's article A Complete Guide to useEffect (which is about much more than useEffect).
Here's a demonstration:
const { useState } = React;
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
let executionContext = 0;
const Example = () => {
const contextNumber = ++executionContext;
const [value, setValue] = useState(0);
const log = msg => console.log(`[${contextNumber}]: ${msg}`);
log(`Example called, value = ${value}`);
const onClick = () => {
log(`onClick`);
delay(800)
.then(() => {
const newValue = Math.floor(Math.random() * 10000);
log(`calling setValue(${newValue})...`);
setValue(newValue);
log(`value is still ${value} (reason #2)`);
});
log(`value is still ${value} (reason #1)`);
};
return (
<div>
{value}
<div>
<input type="button" onClick={onClick} value="Click Me" />
</div>
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

If you are talking about this code:
Authentication.firestore().collection('Health_data')
.doc(localStorage.getItem('user'))
.get()
.then(doc => {
const data = doc.data();
localStorage.setItem('user_data', JSON.stringify(data));
setuserData(data)
console.log(data)
}).catch(function (error) {
console.error("Error reading health", error);
});
console.log(userData)
The problem is
Authentication.firestore().collection('Health_data')
.doc(localStorage.getItem('user'))
.get()
Is async execution, so console.log(userData), sometimes will execute before the Authentication call and the value will be null.
So you need to ensure that the Authentication call has finished

if(userData !== null) {
console.log(userData)
}
this code seems to work for me but I'm not sure how I can use this to access the information on other pages the same way?

Related

Trying to console.log data within useEffect. Not logging any information

function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { accounts } = await res.json();
setAccounts(accounts);
console.log(accounts);
}
fetchAccounts();
}, []);
}
I'm trying to understand why console.log shows nothing in this example and what is the correct way to console.log the data that is being fetched from the api.
Well, you need to get the structure of the returned payload from the API correct. It does not have an accounts property.
The payload looks like this:
{
"success":true,
"data":[{"account":"joejerde","assets":"11933"},{"account":"protonpunks","assets":"9072"}],
"queryTime": 1646267075822
}
So you can rename the data property while destructuring. const { data: accountList } = await res.json();
function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { data: accountList } = await res.json();
setAccounts(accountList);
// logging both the state and the fetched value
console.log(accounts, accountList);
// accounts (state) will be undefined
// if the fetch was successful, accountList will be an array of accounts (as per the API payload)
}
fetchAccounts()
}, [])
return <div>
{JSON.stringify(accounts)}
</div>
}
Edit: using some other variable name while destructuring, confusing to use the same variable name as the state (accounts).
Working codesandbox
One thing I would change is working with try/catch surrounding async/await statements.
If your await statement fails it will never reach the console.log statement.
Unless you have another component handling those errors, I would use it in that way.
That is my suggestion:
function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
try {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { accounts } = await res.json();
setAccounts(accounts);
console.log(accounts);
}
} catch (err) {
console.log(err)
// do something like throw your error
}
fetchAccounts();
}, []);
}
since state function runs asyncronousely . therefore when you use setAccounts it sets accounts variable in async way , so there is a preferred way of doing this thing is as below
problems i seen
1.fetch result should destructured with data instead of accounts variable
2.setAccounts function is running async way so it will not print result immedietly in next line
import { useEffect, useState } from "react";
export default function App() {
const [accounts, setAccounts] = useState();
async function fetchAccounts() {
const res = await fetch(
"https://proton.api.atomicassets.io/atomicassets/v1/accounts"
);
const { data } = await res.json();
setAccounts(data);
}
// on component mount / onload
useState(() => {
fetchAccounts();
}, []);
// on accounts state change
useEffect(() => {
console.log(accounts);
}, [accounts]);
return <div className="blankElement">hello world</div>;
}
check here sample

Firebase Firestore data loads in, but disappears shortly after it has appeared when rendering in react.js

I call the function inside of useEffect. This means the function is not repeatedly called but for some reason it seems as if the data is being removed after a short period of time
const [following, setFollowing] = useState([])
useEffect(() => {
getUser()
getFollowing()
}, []);
The function here fetches all of the users which the current user is currently following. It seems to work fine and console logging the follower's array returns the users which are expected and appear to render on the screen.
async function getFollowing()
{
const followerRef = query(collection(db, "followers"), where("follower", "==", auth.currentUser.uid));
const querySnapshot = await getDocs(followerRef);
let followerArray = []
querySnapshot.forEach((doc) => {
const storageRef = ref(storage, (doc.data().followingEmail) + "/pp");
getDownloadURL(storageRef).then((url) => {
followerArray.push([doc.data().followingEmail, url, doc.data().following]);
}).catch((error) => {
followerArray.push([doc.data().followingEmail, musicIcon, doc.data().following]);
// setFollowing(followerArray)
});
});
console.log("follower array");
console.log(followerArray);
setFollowing(followerArray)
// console.log(following)
}
This is what I am using to loop through all of the elements. The elements do appear in order and formatted correctly on the page but once they have loaded in they somehow disappear. I am not sure what is causing this. Maybe I should make the page wait until the data has loaded in from firebase. Maybe I should have some conditional statement that stops the data from disappearing.
{following.map((f) =>
<Row key={f[2]} className="hover">
<Col xs={2}>
<div className="">
<img src={f[1]} className="smallPhoto" />
</div>
</Col>
<Col xs={10}className="">
<p className= "followText">{f[0]}</p>
</Col>
</Row>
)}
here is what the output looks like for me when refreshing the page
https://giphy.com/gifs/uDtDpY198yCRn6S2CW
I have tried many solutions but none of them are seeming to work.
you are throwing promises inside the foreach, and seting empty array into the state, you should await for the promises to have something inside the array
//this fn should be in another file.
async function getFollowing() {
const followerRef = query(coll(db, "followers"), where(...));
const querySnapshot = await getDocs(followerRef);
let followerArray = [];
const promises = querySnapshot.map((doc) => {
const storageRef = ref(storage, `${followingEmail}/pp`);
// this part is hurting my eyes, xD
// why you are adding [] instead of {} into the array?
return getDownloadURL(storageRef)
.then((url) => followerArray.push([fields]))
.catch((error) => followerArray.push([fields]));
});
});
await Promise.all(promises);
console.log("follower array", followerArray);
return followerArray;
}
//inside your component
const YourComponent = () => {
const [following, setFollowing] = useState([]);
useEffect(() => {
getUser();
getFollowing().then(array => setFollowing(array));
}, []);
};
Thank you so much for the support. The program seems to be working as intended now. The change in useEffect I believed made a key difference. This is what my code looks like now:
}, []);
useEffect(() => {
getUser()
getFollowing().then(array => setFollowing(array));
getFollowers()
}, []);
Changes in getFollowing function that helped to resolve the issue
let getFollowing = async () =>
{
const followerRef = query(collection(db, "followers"), where("follower", "==", JSON.parse(localStorage.getItem("userID"))));
let followerArray = [];
await getDocs(followerRef).then((querySnapshot) =>{
querySnapshot.forEach((doc) => {
const storageRef = ref(storage, (doc.data().followingEmail) + "/pp");
getDownloadURL(storageRef)
.then((url) => { followerArray.push([doc.data().followingEmail, url, doc.data().following]);
})
.catch((error) => {followerArray.push([doc.data().followingEmail, musicIcon, doc.data().following]);
});
})
} )
console.log("followerArray", followerArray)
return followerArray;
}

Async function inside useMemo not working

I have a situation in which I have an object with all the books and I want to get the author info which sits in a different collection. I tried fetching the data inside a useMemo, but I get an error since the promise does not get resolved I guess. How to make useMemo wait for the data to come for the author?
async function useAuthor(authorID:string) {
await firestore.collection('users').doc(authorID).get().then(doc => {
return doc.data();
})
};
const normalizedBooks = useMemo(
() =>
books?.map( (book) => ({
...book,
author: useAuthor(book.authorId),
})),
[books]
);
Fetching remote data should be done in an effect:
function useAuthor(authorID:string) {
const [author, setAuthor] = useState(null);
useEffect(() => {
firestore.collection('users').doc(authorID).get().then(
doc => setAuthor(doc.data())
);
}, [authorID]);
return author;
}
Note that author will be null during the first render and until the request completed. You can improve that example by e.g. adding loading and error states. Or you can also use something like react-query which provides those out of the box:
import useQuery from 'react-query';
function getItemById(collectionID:string, itemID:string) {
return firestore.collection(collectionID).doc(itemID).get().then(
doc => doc.data()
);
}
const Author = ({authorID}) => {
const {data: author, isLoading, error} = useQuery(
['users', authorID],
getItemById
);
if (isLoading) return 'Loading...'
if (error) return 'Failed to fetch author :(';
return <p>{author.name}</p>;
}

How to declare a variable from a firestore query?

this piece of code works when I press save in visual studio code. But If I refresh the preview page in the browser it shows me this error: Unhandled Rejection (FirebaseError): Function Query.where() called with invalid data. Unsupported field value: undefined
let { id } = useParams();
const [video, setVideo] = React.useState([]);
const [show, setShow] = React.useState([]);
const classes = useStyles();
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore();
const data = await db
.collection("videos")
.where('path', '==', id)
.get()
setVideo(data.docs.map(doc => doc.data()));
}
fetchData()
}, [])
let showUrl = video.map(video =>(video.uploadBy));
console.log(showUrl[0]);
let videoDate = video.map(video =>(video.date.toDate()));
console.log(videoDate[0]);
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore();
const data = await db
.collection("shows")
.where('urlPath', '==', showUrl[0])
.get()
setShow(data.docs.map(doc => doc.data()));
}
fetchData()
}, [])
I think that the problem is that I'm trying to declare the variable "showUrl" in the wrong way. The console.log(showUrl[0]) works perfectly. It prints exactly the value that I need.
Both these useEffect calls fire as soon as the component mounts. If you are getting the id for your first useEffect from url parameters or such, it's probably there immediately and the call Firestore query should work.
However, when your second useEffect fires, the state 'video' is still set to an empty array. Therefore the showUrl variable is also an empty array, and showUrl[0] is undefined.
What you could do for your second useEffect is this:
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore();
const data = await db
.collection("shows")
.where('urlPath', '==', showUrl[0])
.get()
setShow(data.docs.map(doc => doc.data()));
}
video.length && fetchData()
}, [video])
So you are only calling the fetchData() function if the array in 'video' state has more than 0 items, and you add it to the useEffect dependency array, so the useEffect gets ran every time 'video' changes.

Mutiple axios in useEffect

I'm trying to use the useEffect with multiple axios request. Here is my code:
const [data, setData] = useState('');
const [popularCategories, setPopularCategories] = useState('');
useEffect(() => {
(async () => {
const result = await axios
.get("http://example.com/api/v1/api1")
.catch((err) => {
console.error(err);
});
await setData(result.data);
const resultCategories = await axios
.get("http://example.com/api/v1/api2")
.catch((err) => {
console.error(err);
});
await setPopularCategories(resultCategories.data);
})();
}, []);
return (
<div>
{data}
{popularCategories}
</div>
);
When I try to use them in return, it prints data, but not popularCategories. I couldn't understand why I can use data and not popularCategories I've checked three times that http://example.com/api/v1/api2 returns a string.
Where am I missing?
Additional: This page is my index page. I've routed domain to directly here.
EDIT: true variable name is popularCategories. Problem still exists.
EDIT 2: I've realized that, when I remove the data and run only popular categories. The problem still exists. Would it be about API response time? Also, I've consol logged it. It print 4 times. First it doesn't give value first two print and it gives last two.
first of all, no sense to use await with setState - it does not return a promise, s awaiting them pointless.
Seems like you mess up with state name in you return:
return (
<div>
{data}
{resultCategories} // should it be popularCategories ?
</div>
);
I feel you need to specify the state object in second parameter. Looks like the dom is not rendering again when state changes.
Try below
const [data, setData] = useState('');
const [popularCategories, setPopularCategories] = useState('');
useEffect(() => {
(async () => {
const result = await axios
.get("http://example.com/api/v1/api1")
.catch((err) => {
console.error(err);
});
await setData(result.data);
const resultCategories = await axios
.get("http://example.com/api/v1/api2")
.catch((err) => {
console.error(err);
});
await setPopularCategories(resultCategories.data);
})();
}, [data,popularCategories]); //specified this
return (
<div>
{data}
{popularCategories}
</div>
);

Categories